mirror of https://github.com/ethereum/go-ethereum
all: on-chain oracle checkpoint syncing (#19543)
* all: implement simple checkpoint syncing cmd, les, node: remove callback mechanism cmd, node: remove callback definition les: simplify the registrar les: expose checkpoint rpc services in the light client les, light: don't store untrusted receipt cmd, contracts, les: discard stale checkpoint cmd, contracts/registrar: loose restriction of registeration cmd, contracts: add replay-protection all: off-chain multi-signature contract params: deploy checkpoint contract for rinkeby cmd/registrar: add raw signing mode for registrar cmd/registrar, contracts/registrar, les: fixed messages * cmd/registrar, contracts/registrar: fix lints * accounts/abi/bind, les: address comments * cmd, contracts, les, light, params: minor checkpoint sync cleanups * cmd, eth, les, light: move checkpoint config to config file * cmd, eth, les, params: address comments * eth, les, params: address comments * cmd: polish up the checkpoint admin CLI * cmd, contracts, params: deploy new version contract * cmd/checkpoint-admin: add another flag for clef mode signing * cmd, contracts, les: rename and regen checkpoint oracle with abigenpull/19773/head
parent
702f52fb99
commit
f7cdea2bdc
@ -0,0 +1,166 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"io/ioutil" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore" |
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils" |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/console" |
||||||
|
"github.com/ethereum/go-ethereum/contracts/checkpointoracle" |
||||||
|
"github.com/ethereum/go-ethereum/ethclient" |
||||||
|
"github.com/ethereum/go-ethereum/params" |
||||||
|
"github.com/ethereum/go-ethereum/rpc" |
||||||
|
"gopkg.in/urfave/cli.v1" |
||||||
|
) |
||||||
|
|
||||||
|
// newClient creates a client with specified remote URL.
|
||||||
|
func newClient(ctx *cli.Context) *ethclient.Client { |
||||||
|
client, err := ethclient.Dial(ctx.GlobalString(nodeURLFlag.Name)) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to connect to Ethereum node: %v", err) |
||||||
|
} |
||||||
|
return client |
||||||
|
} |
||||||
|
|
||||||
|
// newRPCClient creates a rpc client with specified node URL.
|
||||||
|
func newRPCClient(url string) *rpc.Client { |
||||||
|
client, err := rpc.Dial(url) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to connect to Ethereum node: %v", err) |
||||||
|
} |
||||||
|
return client |
||||||
|
} |
||||||
|
|
||||||
|
// getContractAddr retrieves the register contract address through
|
||||||
|
// rpc request.
|
||||||
|
func getContractAddr(client *rpc.Client) common.Address { |
||||||
|
var addr string |
||||||
|
if err := client.Call(&addr, "les_getCheckpointContractAddress"); err != nil { |
||||||
|
utils.Fatalf("Failed to fetch checkpoint oracle address: %v", err) |
||||||
|
} |
||||||
|
return common.HexToAddress(addr) |
||||||
|
} |
||||||
|
|
||||||
|
// getCheckpoint retrieves the specified checkpoint or the latest one
|
||||||
|
// through rpc request.
|
||||||
|
func getCheckpoint(ctx *cli.Context, client *rpc.Client) *params.TrustedCheckpoint { |
||||||
|
var checkpoint *params.TrustedCheckpoint |
||||||
|
|
||||||
|
if ctx.GlobalIsSet(indexFlag.Name) { |
||||||
|
var result [3]string |
||||||
|
index := uint64(ctx.GlobalInt64(indexFlag.Name)) |
||||||
|
if err := client.Call(&result, "les_getCheckpoint", index); err != nil { |
||||||
|
utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err) |
||||||
|
} |
||||||
|
checkpoint = ¶ms.TrustedCheckpoint{ |
||||||
|
SectionIndex: index, |
||||||
|
SectionHead: common.HexToHash(result[0]), |
||||||
|
CHTRoot: common.HexToHash(result[1]), |
||||||
|
BloomRoot: common.HexToHash(result[2]), |
||||||
|
} |
||||||
|
} else { |
||||||
|
var result [4]string |
||||||
|
err := client.Call(&result, "les_latestCheckpoint") |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to get local checkpoint %v, please ensure the les API is exposed", err) |
||||||
|
} |
||||||
|
index, err := strconv.ParseUint(result[0], 0, 64) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to parse checkpoint index %v", err) |
||||||
|
} |
||||||
|
checkpoint = ¶ms.TrustedCheckpoint{ |
||||||
|
SectionIndex: index, |
||||||
|
SectionHead: common.HexToHash(result[1]), |
||||||
|
CHTRoot: common.HexToHash(result[2]), |
||||||
|
BloomRoot: common.HexToHash(result[3]), |
||||||
|
} |
||||||
|
} |
||||||
|
return checkpoint |
||||||
|
} |
||||||
|
|
||||||
|
// newContract creates a registrar contract instance with specified
|
||||||
|
// contract address or the default contracts for mainnet or testnet.
|
||||||
|
func newContract(client *rpc.Client) (common.Address, *checkpointoracle.CheckpointOracle) { |
||||||
|
addr := getContractAddr(client) |
||||||
|
if addr == (common.Address{}) { |
||||||
|
utils.Fatalf("No specified registrar contract address") |
||||||
|
} |
||||||
|
contract, err := checkpointoracle.NewCheckpointOracle(addr, ethclient.NewClient(client)) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to setup registrar contract %s: %v", addr, err) |
||||||
|
} |
||||||
|
return addr, contract |
||||||
|
} |
||||||
|
|
||||||
|
// promptPassphrase prompts the user for a passphrase.
|
||||||
|
// Set confirmation to true to require the user to confirm the passphrase.
|
||||||
|
func promptPassphrase(confirmation bool) string { |
||||||
|
passphrase, err := console.Stdin.PromptPassword("Passphrase: ") |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to read passphrase: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
if confirmation { |
||||||
|
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ") |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to read passphrase confirmation: %v", err) |
||||||
|
} |
||||||
|
if passphrase != confirm { |
||||||
|
utils.Fatalf("Passphrases do not match") |
||||||
|
} |
||||||
|
} |
||||||
|
return passphrase |
||||||
|
} |
||||||
|
|
||||||
|
// getPassphrase obtains a passphrase given by the user. It first checks the
|
||||||
|
// --password command line flag and ultimately prompts the user for a
|
||||||
|
// passphrase.
|
||||||
|
func getPassphrase(ctx *cli.Context) string { |
||||||
|
passphraseFile := ctx.String(utils.PasswordFileFlag.Name) |
||||||
|
if passphraseFile != "" { |
||||||
|
content, err := ioutil.ReadFile(passphraseFile) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to read passphrase file '%s': %v", |
||||||
|
passphraseFile, err) |
||||||
|
} |
||||||
|
return strings.TrimRight(string(content), "\r\n") |
||||||
|
} |
||||||
|
// Otherwise prompt the user for the passphrase.
|
||||||
|
return promptPassphrase(false) |
||||||
|
} |
||||||
|
|
||||||
|
// getKey retrieves the user key through specified key file.
|
||||||
|
func getKey(ctx *cli.Context) *keystore.Key { |
||||||
|
// Read key from file.
|
||||||
|
keyFile := ctx.GlobalString(keyFileFlag.Name) |
||||||
|
keyJson, err := ioutil.ReadFile(keyFile) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to read the keyfile at '%s': %v", keyFile, err) |
||||||
|
} |
||||||
|
// Decrypt key with passphrase.
|
||||||
|
passphrase := getPassphrase(ctx) |
||||||
|
key, err := keystore.DecryptKey(keyJson, passphrase) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to decrypt user key '%s': %v", keyFile, err) |
||||||
|
} |
||||||
|
return key |
||||||
|
} |
@ -0,0 +1,335 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"context" |
||||||
|
"encoding/binary" |
||||||
|
"fmt" |
||||||
|
"math/big" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts" |
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils" |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil" |
||||||
|
"github.com/ethereum/go-ethereum/contracts/checkpointoracle" |
||||||
|
"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract" |
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/ethclient" |
||||||
|
"github.com/ethereum/go-ethereum/log" |
||||||
|
"github.com/ethereum/go-ethereum/params" |
||||||
|
"github.com/ethereum/go-ethereum/rpc" |
||||||
|
"gopkg.in/urfave/cli.v1" |
||||||
|
) |
||||||
|
|
||||||
|
var commandDeploy = cli.Command{ |
||||||
|
Name: "deploy", |
||||||
|
Usage: "Deploy a new checkpoint oracle contract", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
nodeURLFlag, |
||||||
|
clefURLFlag, |
||||||
|
signersFlag, |
||||||
|
thresholdFlag, |
||||||
|
keyFileFlag, |
||||||
|
utils.PasswordFileFlag, |
||||||
|
}, |
||||||
|
Action: utils.MigrateFlags(deploy), |
||||||
|
} |
||||||
|
|
||||||
|
var commandSign = cli.Command{ |
||||||
|
Name: "sign", |
||||||
|
Usage: "Sign the checkpoint with the specified key", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
nodeURLFlag, |
||||||
|
clefURLFlag, |
||||||
|
indexFlag, |
||||||
|
hashFlag, |
||||||
|
oracleFlag, |
||||||
|
keyFileFlag, |
||||||
|
signerFlag, |
||||||
|
utils.PasswordFileFlag, |
||||||
|
}, |
||||||
|
Action: utils.MigrateFlags(sign), |
||||||
|
} |
||||||
|
|
||||||
|
var commandPublish = cli.Command{ |
||||||
|
Name: "publish", |
||||||
|
Usage: "Publish a checkpoint into the oracle", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
nodeURLFlag, |
||||||
|
indexFlag, |
||||||
|
signaturesFlag, |
||||||
|
keyFileFlag, |
||||||
|
utils.PasswordFileFlag, |
||||||
|
}, |
||||||
|
Action: utils.MigrateFlags(publish), |
||||||
|
} |
||||||
|
|
||||||
|
// deploy deploys the checkpoint registrar contract.
|
||||||
|
//
|
||||||
|
// Note the network where the contract is deployed depends on
|
||||||
|
// the network where the connected node is located.
|
||||||
|
func deploy(ctx *cli.Context) error { |
||||||
|
// Gather all the addresses that should be permitted to sign
|
||||||
|
var addrs []common.Address |
||||||
|
for _, account := range strings.Split(ctx.String(signersFlag.Name), ",") { |
||||||
|
if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) { |
||||||
|
utils.Fatalf("Invalid account in --signers: '%s'", trimmed) |
||||||
|
} |
||||||
|
addrs = append(addrs, common.HexToAddress(account)) |
||||||
|
} |
||||||
|
// Retrieve and validate the signing threshold
|
||||||
|
needed := ctx.Int(thresholdFlag.Name) |
||||||
|
if needed == 0 || needed > len(addrs) { |
||||||
|
utils.Fatalf("Invalid signature threshold %d", needed) |
||||||
|
} |
||||||
|
// Print a summary to ensure the user understands what they're signing
|
||||||
|
fmt.Printf("Deploying new checkpoint oracle:\n\n") |
||||||
|
for i, addr := range addrs { |
||||||
|
fmt.Printf("Admin %d => %s\n", i+1, addr.Hex()) |
||||||
|
} |
||||||
|
fmt.Printf("\nSignatures needed to publish: %d\n", needed) |
||||||
|
|
||||||
|
// Retrieve the private key, create an abigen transactor and an RPC client
|
||||||
|
transactor := bind.NewKeyedTransactor(getKey(ctx).PrivateKey) |
||||||
|
client := newClient(ctx) |
||||||
|
|
||||||
|
// Deploy the checkpoint oracle
|
||||||
|
oracle, tx, _, err := contract.DeployCheckpointOracle(transactor, client, addrs, big.NewInt(int64(params.CheckpointFrequency)), |
||||||
|
big.NewInt(int64(params.CheckpointProcessConfirmations)), big.NewInt(int64(needed))) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to deploy checkpoint oracle %v", err) |
||||||
|
} |
||||||
|
log.Info("Deployed checkpoint oracle", "address", oracle, "tx", tx.Hash().Hex()) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// sign creates the signature for specific checkpoint
|
||||||
|
// with local key. Only contract admins have the permission to
|
||||||
|
// sign checkpoint.
|
||||||
|
func sign(ctx *cli.Context) error { |
||||||
|
var ( |
||||||
|
offline bool // The indicator whether we sign checkpoint by offline.
|
||||||
|
chash common.Hash |
||||||
|
cindex uint64 |
||||||
|
address common.Address |
||||||
|
|
||||||
|
node *rpc.Client |
||||||
|
oracle *checkpointoracle.CheckpointOracle |
||||||
|
) |
||||||
|
if !ctx.GlobalIsSet(nodeURLFlag.Name) { |
||||||
|
// Offline mode signing
|
||||||
|
offline = true |
||||||
|
if !ctx.IsSet(hashFlag.Name) { |
||||||
|
utils.Fatalf("Please specify the checkpoint hash (--hash) to sign in offline mode") |
||||||
|
} |
||||||
|
chash = common.HexToHash(ctx.String(hashFlag.Name)) |
||||||
|
|
||||||
|
if !ctx.IsSet(indexFlag.Name) { |
||||||
|
utils.Fatalf("Please specify checkpoint index (--index) to sign in offline mode") |
||||||
|
} |
||||||
|
cindex = ctx.Uint64(indexFlag.Name) |
||||||
|
|
||||||
|
if !ctx.IsSet(oracleFlag.Name) { |
||||||
|
utils.Fatalf("Please specify oracle address (--oracle) to sign in offline mode") |
||||||
|
} |
||||||
|
address = common.HexToAddress(ctx.String(oracleFlag.Name)) |
||||||
|
} else { |
||||||
|
// Interactive mode signing, retrieve the data from the remote node
|
||||||
|
node = newRPCClient(ctx.GlobalString(nodeURLFlag.Name)) |
||||||
|
|
||||||
|
checkpoint := getCheckpoint(ctx, node) |
||||||
|
chash = checkpoint.Hash() |
||||||
|
cindex = checkpoint.SectionIndex |
||||||
|
address = getContractAddr(node) |
||||||
|
|
||||||
|
// Check the validity of checkpoint
|
||||||
|
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) |
||||||
|
defer cancelFn() |
||||||
|
|
||||||
|
head, err := ethclient.NewClient(node).HeaderByNumber(reqCtx, nil) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
num := head.Number.Uint64() |
||||||
|
if num < ((cindex+1)*params.CheckpointFrequency + params.CheckpointProcessConfirmations) { |
||||||
|
utils.Fatalf("Invalid future checkpoint") |
||||||
|
} |
||||||
|
_, oracle = newContract(node) |
||||||
|
latest, _, h, err := oracle.Contract().GetLatestCheckpoint(nil) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if cindex < latest { |
||||||
|
utils.Fatalf("Checkpoint is too old") |
||||||
|
} |
||||||
|
if cindex == latest && (latest != 0 || h.Uint64() != 0) { |
||||||
|
utils.Fatalf("Stale checkpoint, latest registered %d, given %d", latest, cindex) |
||||||
|
} |
||||||
|
} |
||||||
|
var ( |
||||||
|
signature string |
||||||
|
signer string |
||||||
|
) |
||||||
|
// isAdmin checks whether the specified signer is admin.
|
||||||
|
isAdmin := func(addr common.Address) error { |
||||||
|
signers, err := oracle.Contract().GetAllAdmin(nil) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
for _, s := range signers { |
||||||
|
if s == addr { |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
return fmt.Errorf("signer %v is not the admin", addr.Hex()) |
||||||
|
} |
||||||
|
// Print to the user the data thy are about to sign
|
||||||
|
fmt.Printf("Oracle => %s\n", address.Hex()) |
||||||
|
fmt.Printf("Index %4d => %s\n", cindex, chash.Hex()) |
||||||
|
|
||||||
|
switch { |
||||||
|
case ctx.GlobalIsSet(clefURLFlag.Name): |
||||||
|
// Sign checkpoint in clef mode.
|
||||||
|
signer = ctx.String(signerFlag.Name) |
||||||
|
|
||||||
|
if !offline { |
||||||
|
if err := isAdmin(common.HexToAddress(signer)); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
clef := newRPCClient(ctx.GlobalString(clefURLFlag.Name)) |
||||||
|
p := make(map[string]string) |
||||||
|
buf := make([]byte, 8) |
||||||
|
binary.BigEndian.PutUint64(buf, cindex) |
||||||
|
p["address"] = address.Hex() |
||||||
|
p["message"] = hexutil.Encode(append(buf, chash.Bytes()...)) |
||||||
|
if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil { |
||||||
|
utils.Fatalf("Failed to sign checkpoint, err %v", err) |
||||||
|
} |
||||||
|
case ctx.GlobalIsSet(keyFileFlag.Name): |
||||||
|
// Sign checkpoint in raw private key file mode.
|
||||||
|
key := getKey(ctx) |
||||||
|
signer = key.Address.Hex() |
||||||
|
|
||||||
|
if !offline { |
||||||
|
if err := isAdmin(key.Address); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
sig, err := crypto.Sign(sighash(cindex, address, chash), key.PrivateKey) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to sign checkpoint, err %v", err) |
||||||
|
} |
||||||
|
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||||
|
signature = common.Bytes2Hex(sig) |
||||||
|
default: |
||||||
|
utils.Fatalf("Please specify clef URL or private key file path to sign checkpoint") |
||||||
|
} |
||||||
|
fmt.Printf("Signer => %s\n", signer) |
||||||
|
fmt.Printf("Signature => %s\n", signature) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// sighash calculates the hash of the data to sign for the checkpoint oracle.
|
||||||
|
func sighash(index uint64, oracle common.Address, hash common.Hash) []byte { |
||||||
|
buf := make([]byte, 8) |
||||||
|
binary.BigEndian.PutUint64(buf, index) |
||||||
|
|
||||||
|
data := append([]byte{0x19, 0x00}, append(oracle[:], append(buf, hash[:]...)...)...) |
||||||
|
return crypto.Keccak256(data) |
||||||
|
} |
||||||
|
|
||||||
|
// ecrecover calculates the sender address from a sighash and signature combo.
|
||||||
|
func ecrecover(sighash []byte, sig []byte) common.Address { |
||||||
|
sig[64] -= 27 |
||||||
|
defer func() { sig[64] += 27 }() |
||||||
|
|
||||||
|
signer, err := crypto.SigToPub(sighash, sig) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Failed to recover sender from signature %x: %v", sig, err) |
||||||
|
} |
||||||
|
return crypto.PubkeyToAddress(*signer) |
||||||
|
} |
||||||
|
|
||||||
|
// publish registers the specified checkpoint which generated by connected node
|
||||||
|
// with a authorised private key.
|
||||||
|
func publish(ctx *cli.Context) error { |
||||||
|
// Print the checkpoint oracle's current status to make sure we're interacting
|
||||||
|
// with the correct network and contract.
|
||||||
|
status(ctx) |
||||||
|
|
||||||
|
// Gather the signatures from the CLI
|
||||||
|
var sigs [][]byte |
||||||
|
for _, sig := range strings.Split(ctx.String(signaturesFlag.Name), ",") { |
||||||
|
trimmed := strings.TrimPrefix(strings.TrimSpace(sig), "0x") |
||||||
|
if len(trimmed) != 130 { |
||||||
|
utils.Fatalf("Invalid signature in --signature: '%s'", trimmed) |
||||||
|
} else { |
||||||
|
sigs = append(sigs, common.Hex2Bytes(trimmed)) |
||||||
|
} |
||||||
|
} |
||||||
|
// Retrieve the checkpoint we want to sign to sort the signatures
|
||||||
|
var ( |
||||||
|
client = newRPCClient(ctx.GlobalString(nodeURLFlag.Name)) |
||||||
|
addr, oracle = newContract(client) |
||||||
|
checkpoint = getCheckpoint(ctx, client) |
||||||
|
sighash = sighash(checkpoint.SectionIndex, addr, checkpoint.Hash()) |
||||||
|
) |
||||||
|
for i := 0; i < len(sigs); i++ { |
||||||
|
for j := i + 1; j < len(sigs); j++ { |
||||||
|
signerA := ecrecover(sighash, sigs[i]) |
||||||
|
signerB := ecrecover(sighash, sigs[j]) |
||||||
|
if bytes.Compare(signerA.Bytes(), signerB.Bytes()) > 0 { |
||||||
|
sigs[i], sigs[j] = sigs[j], sigs[i] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// Retrieve recent header info to protect replay attack
|
||||||
|
reqCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) |
||||||
|
defer cancelFn() |
||||||
|
|
||||||
|
head, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, nil) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
num := head.Number.Uint64() |
||||||
|
recent, err := ethclient.NewClient(client).HeaderByNumber(reqCtx, big.NewInt(int64(num-128))) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
// Print a summary of the operation that's going to be performed
|
||||||
|
fmt.Printf("Publishing %d => %s:\n\n", checkpoint.SectionIndex, checkpoint.Hash().Hex()) |
||||||
|
for i, sig := range sigs { |
||||||
|
fmt.Printf("Signer %d => %s\n", i+1, ecrecover(sighash, sig).Hex()) |
||||||
|
} |
||||||
|
fmt.Println() |
||||||
|
fmt.Printf("Sentry number => %d\nSentry hash => %s\n", recent.Number, recent.Hash().Hex()) |
||||||
|
|
||||||
|
// Publish the checkpoint into the oracle
|
||||||
|
tx, err := oracle.RegisterCheckpoint(getKey(ctx).PrivateKey, checkpoint.SectionIndex, checkpoint.Hash().Bytes(), recent.Number, recent.Hash(), sigs) |
||||||
|
if err != nil { |
||||||
|
utils.Fatalf("Register contract failed %v", err) |
||||||
|
} |
||||||
|
log.Info("Successfully registered checkpoint", "tx", tx.Hash().Hex()) |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,124 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// checkpoint-admin is a utility that can be used to query checkpoint information
|
||||||
|
// and register stable checkpoints into an oracle contract.
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils" |
||||||
|
"github.com/ethereum/go-ethereum/common/fdlimit" |
||||||
|
"github.com/ethereum/go-ethereum/log" |
||||||
|
"gopkg.in/urfave/cli.v1" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
commandHelperTemplate = `{{.Name}}{{if .Subcommands}} command{{end}}{{if .Flags}} [command options]{{end}} [arguments...] |
||||||
|
{{if .Description}}{{.Description}} |
||||||
|
{{end}}{{if .Subcommands}} |
||||||
|
SUBCOMMANDS: |
||||||
|
{{range .Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} |
||||||
|
{{end}}{{end}}{{if .Flags}} |
||||||
|
OPTIONS: |
||||||
|
{{range $.Flags}}{{"\t"}}{{.}} |
||||||
|
{{end}} |
||||||
|
{{end}}` |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// Git SHA1 commit hash of the release (set via linker flags)
|
||||||
|
gitCommit = "" |
||||||
|
gitDate = "" |
||||||
|
) |
||||||
|
|
||||||
|
var app *cli.App |
||||||
|
|
||||||
|
func init() { |
||||||
|
app = utils.NewApp(gitCommit, gitDate, "ethereum checkpoint helper tool") |
||||||
|
app.Commands = []cli.Command{ |
||||||
|
commandStatus, |
||||||
|
commandDeploy, |
||||||
|
commandSign, |
||||||
|
commandPublish, |
||||||
|
} |
||||||
|
app.Flags = []cli.Flag{ |
||||||
|
oracleFlag, |
||||||
|
keyFileFlag, |
||||||
|
nodeURLFlag, |
||||||
|
clefURLFlag, |
||||||
|
utils.PasswordFileFlag, |
||||||
|
} |
||||||
|
cli.CommandHelpTemplate = commandHelperTemplate |
||||||
|
} |
||||||
|
|
||||||
|
// Commonly used command line flags.
|
||||||
|
var ( |
||||||
|
indexFlag = cli.Int64Flag{ |
||||||
|
Name: "index", |
||||||
|
Usage: "Checkpoint index (query latest from remote node if not specified)", |
||||||
|
} |
||||||
|
hashFlag = cli.StringFlag{ |
||||||
|
Name: "hash", |
||||||
|
Usage: "Checkpoint hash (query latest from remote node if not specified)", |
||||||
|
} |
||||||
|
oracleFlag = cli.StringFlag{ |
||||||
|
Name: "oracle", |
||||||
|
Usage: "Checkpoint oracle address (query from remote node if not specified)", |
||||||
|
} |
||||||
|
thresholdFlag = cli.Int64Flag{ |
||||||
|
Name: "threshold", |
||||||
|
Usage: "Minimal number of signatures required to approve a checkpoint", |
||||||
|
} |
||||||
|
keyFileFlag = cli.StringFlag{ |
||||||
|
Name: "keyfile", |
||||||
|
Usage: "The private key file (keyfile signature is not recommended)", |
||||||
|
} |
||||||
|
nodeURLFlag = cli.StringFlag{ |
||||||
|
Name: "rpc", |
||||||
|
Value: "http://localhost:8545", |
||||||
|
Usage: "The rpc endpoint of a local or remote geth node", |
||||||
|
} |
||||||
|
clefURLFlag = cli.StringFlag{ |
||||||
|
Name: "clef", |
||||||
|
Value: "http://localhost:8550", |
||||||
|
Usage: "The rpc endpoint of clef", |
||||||
|
} |
||||||
|
signerFlag = cli.StringFlag{ |
||||||
|
Name: "signer", |
||||||
|
Usage: "Signer address for clef mode signing", |
||||||
|
} |
||||||
|
signersFlag = cli.StringFlag{ |
||||||
|
Name: "signers", |
||||||
|
Usage: "Comma separated accounts of trusted checkpoint signers", |
||||||
|
} |
||||||
|
signaturesFlag = cli.StringFlag{ |
||||||
|
Name: "signatures", |
||||||
|
Usage: "Comma separated checkpoint signatures to submit", |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) |
||||||
|
fdlimit.Raise(2048) |
||||||
|
|
||||||
|
if err := app.Run(os.Args); err != nil { |
||||||
|
fmt.Fprintln(os.Stderr, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils" |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"gopkg.in/urfave/cli.v1" |
||||||
|
) |
||||||
|
|
||||||
|
var commandStatus = cli.Command{ |
||||||
|
Name: "status", |
||||||
|
Usage: "Fetches the signers and checkpoint status of the oracle contract", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
nodeURLFlag, |
||||||
|
}, |
||||||
|
Action: utils.MigrateFlags(status), |
||||||
|
} |
||||||
|
|
||||||
|
// status fetches the admin list of specified registrar contract.
|
||||||
|
func status(ctx *cli.Context) error { |
||||||
|
// Create a wrapper around the checkpoint oracle contract
|
||||||
|
addr, oracle := newContract(newRPCClient(ctx.GlobalString(nodeURLFlag.Name))) |
||||||
|
fmt.Printf("Oracle => %s\n", addr.Hex()) |
||||||
|
fmt.Println() |
||||||
|
|
||||||
|
// Retrieve the list of authorized signers (admins)
|
||||||
|
admins, err := oracle.Contract().GetAllAdmin(nil) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
for i, admin := range admins { |
||||||
|
fmt.Printf("Admin %d => %s\n", i+1, admin.Hex()) |
||||||
|
} |
||||||
|
fmt.Println() |
||||||
|
|
||||||
|
// Retrieve the latest checkpoint
|
||||||
|
index, checkpoint, height, err := oracle.Contract().GetLatestCheckpoint(nil) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
fmt.Printf("Checkpoint (published at #%d) %d => %s\n", height, index, common.Hash(checkpoint).Hex()) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,415 @@ |
|||||||
|
// Code generated - DO NOT EDIT.
|
||||||
|
// This file is a generated binding and any manual changes will be lost.
|
||||||
|
|
||||||
|
package contract |
||||||
|
|
||||||
|
import ( |
||||||
|
"math/big" |
||||||
|
"strings" |
||||||
|
|
||||||
|
ethereum "github.com/ethereum/go-ethereum" |
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi" |
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/core/types" |
||||||
|
"github.com/ethereum/go-ethereum/event" |
||||||
|
) |
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var ( |
||||||
|
_ = big.NewInt |
||||||
|
_ = strings.NewReader |
||||||
|
_ = ethereum.NotFound |
||||||
|
_ = abi.U256 |
||||||
|
_ = bind.Bind |
||||||
|
_ = common.Big1 |
||||||
|
_ = types.BloomLookup |
||||||
|
_ = event.NewSubscription |
||||||
|
) |
||||||
|
|
||||||
|
// CheckpointOracleABI is the input ABI used to generate the binding from.
|
||||||
|
const CheckpointOracleABI = "[{\"constant\":true,\"inputs\":[],\"name\":\"GetAllAdmin\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"GetLatestCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"uint64\"},{\"name\":\"\",\"type\":\"bytes32\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_recentNumber\",\"type\":\"uint256\"},{\"name\":\"_recentHash\",\"type\":\"bytes32\"},{\"name\":\"_hash\",\"type\":\"bytes32\"},{\"name\":\"_sectionIndex\",\"type\":\"uint64\"},{\"name\":\"v\",\"type\":\"uint8[]\"},{\"name\":\"r\",\"type\":\"bytes32[]\"},{\"name\":\"s\",\"type\":\"bytes32[]\"}],\"name\":\"SetCheckpoint\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_adminlist\",\"type\":\"address[]\"},{\"name\":\"_sectionSize\",\"type\":\"uint256\"},{\"name\":\"_processConfirms\",\"type\":\"uint256\"},{\"name\":\"_threshold\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"index\",\"type\":\"uint64\"},{\"indexed\":false,\"name\":\"checkpointHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"v\",\"type\":\"uint8\"},{\"indexed\":false,\"name\":\"r\",\"type\":\"bytes32\"},{\"indexed\":false,\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"NewCheckpointVote\",\"type\":\"event\"}]" |
||||||
|
|
||||||
|
// CheckpointOracleBin is the compiled bytecode used for deploying new contracts.
|
||||||
|
const CheckpointOracleBin = `0x608060405234801561001057600080fd5b506040516108153803806108158339818101604052608081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8201602081018481111561005e57600080fd5b815185602082028301116401000000008211171561007b57600080fd5b505060208201516040830151606090930151919450925060005b84518110156101415760016000808784815181106100af57fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff02191690831515021790555060018582815181106100fc57fe5b60209081029190910181015182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b039093169290921790915501610095565b50600592909255600655600755506106b78061015e6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806345848dfc146100465780634d6a304c1461009e578063d459fc46146100cf575b600080fd5b61004e6102b0565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561008a578181015183820152602001610072565b505050509050019250505060405180910390f35b6100a661034f565b6040805167ffffffffffffffff9094168452602084019290925282820152519081900360600190f35b61029c600480360360e08110156100e557600080fd5b81359160208101359160408201359167ffffffffffffffff6060820135169181019060a08101608082013564010000000081111561012257600080fd5b82018360208201111561013457600080fd5b8035906020019184602083028401116401000000008311171561015657600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101a657600080fd5b8201836020820111156101b857600080fd5b803590602001918460208302840111640100000000831117156101da57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561022a57600080fd5b82018360208201111561023c57600080fd5b8035906020019184602083028401116401000000008311171561025e57600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092955061036a945050505050565b604080519115158252519081900360200190f35b6060806001805490506040519080825280602002602001820160405280156102e2578160200160208202803883390190505b50905060005b60015481101561034957600181815481106102ff57fe5b9060005260206000200160009054906101000a90046001600160a01b031682828151811061032957fe5b6001600160a01b03909216602092830291909101909101526001016102e8565b50905090565b60025460045460035467ffffffffffffffff90921691909192565b3360009081526020819052604081205460ff1661038657600080fd5b8688401461039357600080fd5b82518451146103a157600080fd5b81518451146103af57600080fd5b6006546005548660010167ffffffffffffffff1602014310156103d457506000610677565b60025467ffffffffffffffff90811690861610156103f457506000610677565b60025467ffffffffffffffff8681169116148015610426575067ffffffffffffffff8516151580610426575060035415155b1561043357506000610677565b8561044057506000610677565b60408051601960f81b6020808301919091526000602183018190523060601b60228401526001600160c01b031960c08a901b166036840152603e8084018b905284518085039091018152605e909301909352815191012090805b86518110156106715760006001848984815181106104b457fe5b60200260200101518985815181106104c857fe5b60200260200101518986815181106104dc57fe5b602002602001015160405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa15801561053b573d6000803e3d6000fd5b505060408051601f1901516001600160a01b03811660009081526020819052919091205490925060ff16905061057057600080fd5b826001600160a01b0316816001600160a01b03161161058e57600080fd5b8092508867ffffffffffffffff167fce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a418b8a85815181106105ca57fe5b60200260200101518a86815181106105de57fe5b60200260200101518a87815181106105f257fe5b6020026020010151604051808581526020018460ff1660ff16815260200183815260200182815260200194505050505060405180910390a260075482600101106106685750505060048790555050436003556002805467ffffffffffffffff191667ffffffffffffffff86161790556001610677565b5060010161049a565b50600080fd5b97965050505050505056fea265627a7a723058207f6a191ce575596a2f1e907c8c0a01003d16b69fb2c4f432d10878e8c0a99a0264736f6c634300050a0032` |
||||||
|
|
||||||
|
// DeployCheckpointOracle deploys a new Ethereum contract, binding an instance of CheckpointOracle to it.
|
||||||
|
func DeployCheckpointOracle(auth *bind.TransactOpts, backend bind.ContractBackend, _adminlist []common.Address, _sectionSize *big.Int, _processConfirms *big.Int, _threshold *big.Int) (common.Address, *types.Transaction, *CheckpointOracle, error) { |
||||||
|
parsed, err := abi.JSON(strings.NewReader(CheckpointOracleABI)) |
||||||
|
if err != nil { |
||||||
|
return common.Address{}, nil, nil, err |
||||||
|
} |
||||||
|
address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(CheckpointOracleBin), backend, _adminlist, _sectionSize, _processConfirms, _threshold) |
||||||
|
if err != nil { |
||||||
|
return common.Address{}, nil, nil, err |
||||||
|
} |
||||||
|
return address, tx, &CheckpointOracle{CheckpointOracleCaller: CheckpointOracleCaller{contract: contract}, CheckpointOracleTransactor: CheckpointOracleTransactor{contract: contract}, CheckpointOracleFilterer: CheckpointOracleFilterer{contract: contract}}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracle is an auto generated Go binding around an Ethereum contract.
|
||||||
|
type CheckpointOracle struct { |
||||||
|
CheckpointOracleCaller // Read-only binding to the contract
|
||||||
|
CheckpointOracleTransactor // Write-only binding to the contract
|
||||||
|
CheckpointOracleFilterer // Log filterer for contract events
|
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleCaller is an auto generated read-only Go binding around an Ethereum contract.
|
||||||
|
type CheckpointOracleCaller struct { |
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleTransactor is an auto generated write-only Go binding around an Ethereum contract.
|
||||||
|
type CheckpointOracleTransactor struct { |
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
|
||||||
|
type CheckpointOracleFilterer struct { |
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleSession is an auto generated Go binding around an Ethereum contract,
|
||||||
|
// with pre-set call and transact options.
|
||||||
|
type CheckpointOracleSession struct { |
||||||
|
Contract *CheckpointOracle // Generic contract binding to set the session for
|
||||||
|
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||||
|
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleCallerSession is an auto generated read-only Go binding around an Ethereum contract,
|
||||||
|
// with pre-set call options.
|
||||||
|
type CheckpointOracleCallerSession struct { |
||||||
|
Contract *CheckpointOracleCaller // Generic contract caller binding to set the session for
|
||||||
|
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
|
||||||
|
// with pre-set transact options.
|
||||||
|
type CheckpointOracleTransactorSession struct { |
||||||
|
Contract *CheckpointOracleTransactor // Generic contract transactor binding to set the session for
|
||||||
|
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleRaw is an auto generated low-level Go binding around an Ethereum contract.
|
||||||
|
type CheckpointOracleRaw struct { |
||||||
|
Contract *CheckpointOracle // Generic contract binding to access the raw methods on
|
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
|
||||||
|
type CheckpointOracleCallerRaw struct { |
||||||
|
Contract *CheckpointOracleCaller // Generic read-only contract binding to access the raw methods on
|
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
|
||||||
|
type CheckpointOracleTransactorRaw struct { |
||||||
|
Contract *CheckpointOracleTransactor // Generic write-only contract binding to access the raw methods on
|
||||||
|
} |
||||||
|
|
||||||
|
// NewCheckpointOracle creates a new instance of CheckpointOracle, bound to a specific deployed contract.
|
||||||
|
func NewCheckpointOracle(address common.Address, backend bind.ContractBackend) (*CheckpointOracle, error) { |
||||||
|
contract, err := bindCheckpointOracle(address, backend, backend, backend) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &CheckpointOracle{CheckpointOracleCaller: CheckpointOracleCaller{contract: contract}, CheckpointOracleTransactor: CheckpointOracleTransactor{contract: contract}, CheckpointOracleFilterer: CheckpointOracleFilterer{contract: contract}}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// NewCheckpointOracleCaller creates a new read-only instance of CheckpointOracle, bound to a specific deployed contract.
|
||||||
|
func NewCheckpointOracleCaller(address common.Address, caller bind.ContractCaller) (*CheckpointOracleCaller, error) { |
||||||
|
contract, err := bindCheckpointOracle(address, caller, nil, nil) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &CheckpointOracleCaller{contract: contract}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// NewCheckpointOracleTransactor creates a new write-only instance of CheckpointOracle, bound to a specific deployed contract.
|
||||||
|
func NewCheckpointOracleTransactor(address common.Address, transactor bind.ContractTransactor) (*CheckpointOracleTransactor, error) { |
||||||
|
contract, err := bindCheckpointOracle(address, nil, transactor, nil) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &CheckpointOracleTransactor{contract: contract}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// NewCheckpointOracleFilterer creates a new log filterer instance of CheckpointOracle, bound to a specific deployed contract.
|
||||||
|
func NewCheckpointOracleFilterer(address common.Address, filterer bind.ContractFilterer) (*CheckpointOracleFilterer, error) { |
||||||
|
contract, err := bindCheckpointOracle(address, nil, nil, filterer) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &CheckpointOracleFilterer{contract: contract}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// bindCheckpointOracle binds a generic wrapper to an already deployed contract.
|
||||||
|
func bindCheckpointOracle(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { |
||||||
|
parsed, err := abi.JSON(strings.NewReader(CheckpointOracleABI)) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil |
||||||
|
} |
||||||
|
|
||||||
|
// Call invokes the (constant) contract method with params as input values and
|
||||||
|
// sets the output to result. The result type might be a single field for simple
|
||||||
|
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||||
|
// returns.
|
||||||
|
func (_CheckpointOracle *CheckpointOracleRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { |
||||||
|
return _CheckpointOracle.Contract.CheckpointOracleCaller.contract.Call(opts, result, method, params...) |
||||||
|
} |
||||||
|
|
||||||
|
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||||
|
// its default method if one is available.
|
||||||
|
func (_CheckpointOracle *CheckpointOracleRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { |
||||||
|
return _CheckpointOracle.Contract.CheckpointOracleTransactor.contract.Transfer(opts) |
||||||
|
} |
||||||
|
|
||||||
|
// Transact invokes the (paid) contract method with params as input values.
|
||||||
|
func (_CheckpointOracle *CheckpointOracleRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { |
||||||
|
return _CheckpointOracle.Contract.CheckpointOracleTransactor.contract.Transact(opts, method, params...) |
||||||
|
} |
||||||
|
|
||||||
|
// Call invokes the (constant) contract method with params as input values and
|
||||||
|
// sets the output to result. The result type might be a single field for simple
|
||||||
|
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||||
|
// returns.
|
||||||
|
func (_CheckpointOracle *CheckpointOracleCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { |
||||||
|
return _CheckpointOracle.Contract.contract.Call(opts, result, method, params...) |
||||||
|
} |
||||||
|
|
||||||
|
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||||
|
// its default method if one is available.
|
||||||
|
func (_CheckpointOracle *CheckpointOracleTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { |
||||||
|
return _CheckpointOracle.Contract.contract.Transfer(opts) |
||||||
|
} |
||||||
|
|
||||||
|
// Transact invokes the (paid) contract method with params as input values.
|
||||||
|
func (_CheckpointOracle *CheckpointOracleTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { |
||||||
|
return _CheckpointOracle.Contract.contract.Transact(opts, method, params...) |
||||||
|
} |
||||||
|
|
||||||
|
// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
|
||||||
|
//
|
||||||
|
// Solidity: function GetAllAdmin() constant returns(address[])
|
||||||
|
func (_CheckpointOracle *CheckpointOracleCaller) GetAllAdmin(opts *bind.CallOpts) ([]common.Address, error) { |
||||||
|
var ( |
||||||
|
ret0 = new([]common.Address) |
||||||
|
) |
||||||
|
out := ret0 |
||||||
|
err := _CheckpointOracle.contract.Call(opts, out, "GetAllAdmin") |
||||||
|
return *ret0, err |
||||||
|
} |
||||||
|
|
||||||
|
// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
|
||||||
|
//
|
||||||
|
// Solidity: function GetAllAdmin() constant returns(address[])
|
||||||
|
func (_CheckpointOracle *CheckpointOracleSession) GetAllAdmin() ([]common.Address, error) { |
||||||
|
return _CheckpointOracle.Contract.GetAllAdmin(&_CheckpointOracle.CallOpts) |
||||||
|
} |
||||||
|
|
||||||
|
// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
|
||||||
|
//
|
||||||
|
// Solidity: function GetAllAdmin() constant returns(address[])
|
||||||
|
func (_CheckpointOracle *CheckpointOracleCallerSession) GetAllAdmin() ([]common.Address, error) { |
||||||
|
return _CheckpointOracle.Contract.GetAllAdmin(&_CheckpointOracle.CallOpts) |
||||||
|
} |
||||||
|
|
||||||
|
// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
|
||||||
|
//
|
||||||
|
// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
|
||||||
|
func (_CheckpointOracle *CheckpointOracleCaller) GetLatestCheckpoint(opts *bind.CallOpts) (uint64, [32]byte, *big.Int, error) { |
||||||
|
var ( |
||||||
|
ret0 = new(uint64) |
||||||
|
ret1 = new([32]byte) |
||||||
|
ret2 = new(*big.Int) |
||||||
|
) |
||||||
|
out := &[]interface{}{ |
||||||
|
ret0, |
||||||
|
ret1, |
||||||
|
ret2, |
||||||
|
} |
||||||
|
err := _CheckpointOracle.contract.Call(opts, out, "GetLatestCheckpoint") |
||||||
|
return *ret0, *ret1, *ret2, err |
||||||
|
} |
||||||
|
|
||||||
|
// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
|
||||||
|
//
|
||||||
|
// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
|
||||||
|
func (_CheckpointOracle *CheckpointOracleSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) { |
||||||
|
return _CheckpointOracle.Contract.GetLatestCheckpoint(&_CheckpointOracle.CallOpts) |
||||||
|
} |
||||||
|
|
||||||
|
// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
|
||||||
|
//
|
||||||
|
// Solidity: function GetLatestCheckpoint() constant returns(uint64, bytes32, uint256)
|
||||||
|
func (_CheckpointOracle *CheckpointOracleCallerSession) GetLatestCheckpoint() (uint64, [32]byte, *big.Int, error) { |
||||||
|
return _CheckpointOracle.Contract.GetLatestCheckpoint(&_CheckpointOracle.CallOpts) |
||||||
|
} |
||||||
|
|
||||||
|
// SetCheckpoint is a paid mutator transaction binding the contract method 0xd459fc46.
|
||||||
|
//
|
||||||
|
// Solidity: function SetCheckpoint(uint256 _recentNumber, bytes32 _recentHash, bytes32 _hash, uint64 _sectionIndex, uint8[] v, bytes32[] r, bytes32[] s) returns(bool)
|
||||||
|
func (_CheckpointOracle *CheckpointOracleTransactor) SetCheckpoint(opts *bind.TransactOpts, _recentNumber *big.Int, _recentHash [32]byte, _hash [32]byte, _sectionIndex uint64, v []uint8, r [][32]byte, s [][32]byte) (*types.Transaction, error) { |
||||||
|
return _CheckpointOracle.contract.Transact(opts, "SetCheckpoint", _recentNumber, _recentHash, _hash, _sectionIndex, v, r, s) |
||||||
|
} |
||||||
|
|
||||||
|
// SetCheckpoint is a paid mutator transaction binding the contract method 0xd459fc46.
|
||||||
|
//
|
||||||
|
// Solidity: function SetCheckpoint(uint256 _recentNumber, bytes32 _recentHash, bytes32 _hash, uint64 _sectionIndex, uint8[] v, bytes32[] r, bytes32[] s) returns(bool)
|
||||||
|
func (_CheckpointOracle *CheckpointOracleSession) SetCheckpoint(_recentNumber *big.Int, _recentHash [32]byte, _hash [32]byte, _sectionIndex uint64, v []uint8, r [][32]byte, s [][32]byte) (*types.Transaction, error) { |
||||||
|
return _CheckpointOracle.Contract.SetCheckpoint(&_CheckpointOracle.TransactOpts, _recentNumber, _recentHash, _hash, _sectionIndex, v, r, s) |
||||||
|
} |
||||||
|
|
||||||
|
// SetCheckpoint is a paid mutator transaction binding the contract method 0xd459fc46.
|
||||||
|
//
|
||||||
|
// Solidity: function SetCheckpoint(uint256 _recentNumber, bytes32 _recentHash, bytes32 _hash, uint64 _sectionIndex, uint8[] v, bytes32[] r, bytes32[] s) returns(bool)
|
||||||
|
func (_CheckpointOracle *CheckpointOracleTransactorSession) SetCheckpoint(_recentNumber *big.Int, _recentHash [32]byte, _hash [32]byte, _sectionIndex uint64, v []uint8, r [][32]byte, s [][32]byte) (*types.Transaction, error) { |
||||||
|
return _CheckpointOracle.Contract.SetCheckpoint(&_CheckpointOracle.TransactOpts, _recentNumber, _recentHash, _hash, _sectionIndex, v, r, s) |
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleNewCheckpointVoteIterator is returned from FilterNewCheckpointVote and is used to iterate over the raw logs and unpacked data for NewCheckpointVote events raised by the CheckpointOracle contract.
|
||||||
|
type CheckpointOracleNewCheckpointVoteIterator struct { |
||||||
|
Event *CheckpointOracleNewCheckpointVote // Event containing the contract specifics and raw log
|
||||||
|
|
||||||
|
contract *bind.BoundContract // Generic contract to use for unpacking event data
|
||||||
|
event string // Event name to use for unpacking event data
|
||||||
|
|
||||||
|
logs chan types.Log // Log channel receiving the found contract events
|
||||||
|
sub ethereum.Subscription // Subscription for errors, completion and termination
|
||||||
|
done bool // Whether the subscription completed delivering logs
|
||||||
|
fail error // Occurred error to stop iteration
|
||||||
|
} |
||||||
|
|
||||||
|
// Next advances the iterator to the subsequent event, returning whether there
|
||||||
|
// are any more events found. In case of a retrieval or parsing error, false is
|
||||||
|
// returned and Error() can be queried for the exact failure.
|
||||||
|
func (it *CheckpointOracleNewCheckpointVoteIterator) Next() bool { |
||||||
|
// If the iterator failed, stop iterating
|
||||||
|
if it.fail != nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
// If the iterator completed, deliver directly whatever's available
|
||||||
|
if it.done { |
||||||
|
select { |
||||||
|
case log := <-it.logs: |
||||||
|
it.Event = new(CheckpointOracleNewCheckpointVote) |
||||||
|
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { |
||||||
|
it.fail = err |
||||||
|
return false |
||||||
|
} |
||||||
|
it.Event.Raw = log |
||||||
|
return true |
||||||
|
|
||||||
|
default: |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
// Iterator still in progress, wait for either a data or an error event
|
||||||
|
select { |
||||||
|
case log := <-it.logs: |
||||||
|
it.Event = new(CheckpointOracleNewCheckpointVote) |
||||||
|
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { |
||||||
|
it.fail = err |
||||||
|
return false |
||||||
|
} |
||||||
|
it.Event.Raw = log |
||||||
|
return true |
||||||
|
|
||||||
|
case err := <-it.sub.Err(): |
||||||
|
it.done = true |
||||||
|
it.fail = err |
||||||
|
return it.Next() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Error returns any retrieval or parsing error occurred during filtering.
|
||||||
|
func (it *CheckpointOracleNewCheckpointVoteIterator) Error() error { |
||||||
|
return it.fail |
||||||
|
} |
||||||
|
|
||||||
|
// Close terminates the iteration process, releasing any pending underlying
|
||||||
|
// resources.
|
||||||
|
func (it *CheckpointOracleNewCheckpointVoteIterator) Close() error { |
||||||
|
it.sub.Unsubscribe() |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// CheckpointOracleNewCheckpointVote represents a NewCheckpointVote event raised by the CheckpointOracle contract.
|
||||||
|
type CheckpointOracleNewCheckpointVote struct { |
||||||
|
Index uint64 |
||||||
|
CheckpointHash [32]byte |
||||||
|
V uint8 |
||||||
|
R [32]byte |
||||||
|
S [32]byte |
||||||
|
Raw types.Log // Blockchain specific contextual infos
|
||||||
|
} |
||||||
|
|
||||||
|
// FilterNewCheckpointVote is a free log retrieval operation binding the contract event 0xce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a41.
|
||||||
|
//
|
||||||
|
// Solidity: event NewCheckpointVote(uint64 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s)
|
||||||
|
func (_CheckpointOracle *CheckpointOracleFilterer) FilterNewCheckpointVote(opts *bind.FilterOpts, index []uint64) (*CheckpointOracleNewCheckpointVoteIterator, error) { |
||||||
|
|
||||||
|
var indexRule []interface{} |
||||||
|
for _, indexItem := range index { |
||||||
|
indexRule = append(indexRule, indexItem) |
||||||
|
} |
||||||
|
|
||||||
|
logs, sub, err := _CheckpointOracle.contract.FilterLogs(opts, "NewCheckpointVote", indexRule) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &CheckpointOracleNewCheckpointVoteIterator{contract: _CheckpointOracle.contract, event: "NewCheckpointVote", logs: logs, sub: sub}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// WatchNewCheckpointVote is a free log subscription operation binding the contract event 0xce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a41.
|
||||||
|
//
|
||||||
|
// Solidity: event NewCheckpointVote(uint64 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s)
|
||||||
|
func (_CheckpointOracle *CheckpointOracleFilterer) WatchNewCheckpointVote(opts *bind.WatchOpts, sink chan<- *CheckpointOracleNewCheckpointVote, index []uint64) (event.Subscription, error) { |
||||||
|
|
||||||
|
var indexRule []interface{} |
||||||
|
for _, indexItem := range index { |
||||||
|
indexRule = append(indexRule, indexItem) |
||||||
|
} |
||||||
|
|
||||||
|
logs, sub, err := _CheckpointOracle.contract.WatchLogs(opts, "NewCheckpointVote", indexRule) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return event.NewSubscription(func(quit <-chan struct{}) error { |
||||||
|
defer sub.Unsubscribe() |
||||||
|
for { |
||||||
|
select { |
||||||
|
case log := <-logs: |
||||||
|
// New log arrived, parse the event and forward to the user
|
||||||
|
event := new(CheckpointOracleNewCheckpointVote) |
||||||
|
if err := _CheckpointOracle.contract.UnpackLog(event, "NewCheckpointVote", log); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
event.Raw = log |
||||||
|
|
||||||
|
select { |
||||||
|
case sink <- event: |
||||||
|
case err := <-sub.Err(): |
||||||
|
return err |
||||||
|
case <-quit: |
||||||
|
return nil |
||||||
|
} |
||||||
|
case err := <-sub.Err(): |
||||||
|
return err |
||||||
|
case <-quit: |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
}), nil |
||||||
|
} |
||||||
|
|
||||||
|
// ParseNewCheckpointVote is a log parse operation binding the contract event 0xce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a41.
|
||||||
|
//
|
||||||
|
// Solidity: event NewCheckpointVote(uint64 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s)
|
||||||
|
func (_CheckpointOracle *CheckpointOracleFilterer) ParseNewCheckpointVote(log types.Log) (*CheckpointOracleNewCheckpointVote, error) { |
||||||
|
event := new(CheckpointOracleNewCheckpointVote) |
||||||
|
if err := _CheckpointOracle.contract.UnpackLog(event, "NewCheckpointVote", log); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return event, nil |
||||||
|
} |
@ -0,0 +1,174 @@ |
|||||||
|
pragma solidity ^0.5.10; |
||||||
|
|
||||||
|
/** |
||||||
|
* @title CheckpointOracle |
||||||
|
* @author Gary Rong<garyrong@ethereum.org>, Martin Swende <martin.swende@ethereum.org> |
||||||
|
* @dev Implementation of the blockchain checkpoint registrar. |
||||||
|
*/ |
||||||
|
contract CheckpointOracle { |
||||||
|
/* |
||||||
|
Events |
||||||
|
*/ |
||||||
|
|
||||||
|
// NewCheckpointVote is emitted when a new checkpoint proposal receives a vote. |
||||||
|
event NewCheckpointVote(uint64 indexed index, bytes32 checkpointHash, uint8 v, bytes32 r, bytes32 s); |
||||||
|
|
||||||
|
/* |
||||||
|
Public Functions |
||||||
|
*/ |
||||||
|
constructor(address[] memory _adminlist, uint _sectionSize, uint _processConfirms, uint _threshold) public { |
||||||
|
for (uint i = 0; i < _adminlist.length; i++) { |
||||||
|
admins[_adminlist[i]] = true; |
||||||
|
adminList.push(_adminlist[i]); |
||||||
|
} |
||||||
|
sectionSize = _sectionSize; |
||||||
|
processConfirms = _processConfirms; |
||||||
|
threshold = _threshold; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Get latest stable checkpoint information. |
||||||
|
* @return section index |
||||||
|
* @return checkpoint hash |
||||||
|
* @return block height associated with checkpoint |
||||||
|
*/ |
||||||
|
function GetLatestCheckpoint() |
||||||
|
view |
||||||
|
public |
||||||
|
returns(uint64, bytes32, uint) { |
||||||
|
return (sectionIndex, hash, height); |
||||||
|
} |
||||||
|
|
||||||
|
// SetCheckpoint sets a new checkpoint. It accepts a list of signatures |
||||||
|
// @_recentNumber: a recent blocknumber, for replay protection |
||||||
|
// @_recentHash : the hash of `_recentNumber` |
||||||
|
// @_hash : the hash to set at _sectionIndex |
||||||
|
// @_sectionIndex : the section index to set |
||||||
|
// @v : the list of v-values |
||||||
|
// @r : the list or r-values |
||||||
|
// @s : the list of s-values |
||||||
|
function SetCheckpoint( |
||||||
|
uint _recentNumber, |
||||||
|
bytes32 _recentHash, |
||||||
|
bytes32 _hash, |
||||||
|
uint64 _sectionIndex, |
||||||
|
uint8[] memory v, |
||||||
|
bytes32[] memory r, |
||||||
|
bytes32[] memory s) |
||||||
|
public |
||||||
|
returns (bool) |
||||||
|
{ |
||||||
|
// Ensure the sender is authorized. |
||||||
|
require(admins[msg.sender]); |
||||||
|
|
||||||
|
// These checks replay protection, so it cannot be replayed on forks, |
||||||
|
// accidentally or intentionally |
||||||
|
require(blockhash(_recentNumber) == _recentHash); |
||||||
|
|
||||||
|
// Ensure the batch of signatures are valid. |
||||||
|
require(v.length == r.length); |
||||||
|
require(v.length == s.length); |
||||||
|
|
||||||
|
// Filter out "future" checkpoint. |
||||||
|
if (block.number < (_sectionIndex+1)*sectionSize+processConfirms) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
// Filter out "old" announcement |
||||||
|
if (_sectionIndex < sectionIndex) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
// Filter out "stale" announcement |
||||||
|
if (_sectionIndex == sectionIndex && (_sectionIndex != 0 || height != 0)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
// Filter out "invalid" announcement |
||||||
|
if (_hash == ""){ |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// EIP 191 style signatures |
||||||
|
// |
||||||
|
// Arguments when calculating hash to validate |
||||||
|
// 1: byte(0x19) - the initial 0x19 byte |
||||||
|
// 2: byte(0) - the version byte (data with intended validator) |
||||||
|
// 3: this - the validator address |
||||||
|
// -- Application specific data |
||||||
|
// 4 : checkpoint section_index(uint64) |
||||||
|
// 5 : checkpoint hash (bytes32) |
||||||
|
// hash = keccak256(checkpoint_index, section_head, cht_root, bloom_root) |
||||||
|
bytes32 signedHash = keccak256(abi.encodePacked(byte(0x19), byte(0), this, _sectionIndex, _hash)); |
||||||
|
|
||||||
|
address lastVoter = address(0); |
||||||
|
|
||||||
|
// In order for us not to have to maintain a mapping of who has already |
||||||
|
// voted, and we don't want to count a vote twice, the signatures must |
||||||
|
// be submitted in strict ordering. |
||||||
|
for (uint idx = 0; idx < v.length; idx++){ |
||||||
|
address signer = ecrecover(signedHash, v[idx], r[idx], s[idx]); |
||||||
|
require(admins[signer]); |
||||||
|
require(uint256(signer) > uint256(lastVoter)); |
||||||
|
lastVoter = signer; |
||||||
|
emit NewCheckpointVote(_sectionIndex, _hash, v[idx], r[idx], s[idx]); |
||||||
|
|
||||||
|
// Sufficient signatures present, update latest checkpoint. |
||||||
|
if (idx+1 >= threshold){ |
||||||
|
hash = _hash; |
||||||
|
height = block.number; |
||||||
|
sectionIndex = _sectionIndex; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
// We shouldn't wind up here, reverting un-emits the events |
||||||
|
revert(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @dev Get all admin addresses |
||||||
|
* @return address list |
||||||
|
*/ |
||||||
|
function GetAllAdmin() |
||||||
|
public |
||||||
|
view |
||||||
|
returns(address[] memory) |
||||||
|
{ |
||||||
|
address[] memory ret = new address[](adminList.length); |
||||||
|
for (uint i = 0; i < adminList.length; i++) { |
||||||
|
ret[i] = adminList[i]; |
||||||
|
} |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
Fields |
||||||
|
*/ |
||||||
|
// A map of admin users who have the permission to update CHT and bloom Trie root |
||||||
|
mapping(address => bool) admins; |
||||||
|
|
||||||
|
// A list of admin users so that we can obtain all admin users. |
||||||
|
address[] adminList; |
||||||
|
|
||||||
|
// Latest stored section id |
||||||
|
uint64 sectionIndex; |
||||||
|
|
||||||
|
// The block height associated with latest registered checkpoint. |
||||||
|
uint height; |
||||||
|
|
||||||
|
// The hash of latest registered checkpoint. |
||||||
|
bytes32 hash; |
||||||
|
|
||||||
|
// The frequency for creating a checkpoint |
||||||
|
// |
||||||
|
// The default value should be the same as the checkpoint size(32768) in the ethereum. |
||||||
|
uint sectionSize; |
||||||
|
|
||||||
|
// The number of confirmations needed before a checkpoint can be registered. |
||||||
|
// We have to make sure the checkpoint registered will not be invalid due to |
||||||
|
// chain reorg. |
||||||
|
// |
||||||
|
// The default value should be the same as the checkpoint process confirmations(256) |
||||||
|
// in the ethereum. |
||||||
|
uint processConfirms; |
||||||
|
|
||||||
|
// The required signatures to finalize a stable checkpoint. |
||||||
|
uint threshold; |
||||||
|
} |
@ -0,0 +1,91 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package checkpointoracle is a an on-chain light client checkpoint oracle.
|
||||||
|
package checkpointoracle |
||||||
|
|
||||||
|
//go:generate abigen --sol contract/oracle.sol --pkg contract --out contract/oracle.go
|
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/ecdsa" |
||||||
|
"errors" |
||||||
|
"math/big" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract" |
||||||
|
"github.com/ethereum/go-ethereum/core/types" |
||||||
|
) |
||||||
|
|
||||||
|
// CheckpointOracle is a Go wrapper around an on-chain light client checkpoint oracle.
|
||||||
|
type CheckpointOracle struct { |
||||||
|
contract *contract.CheckpointOracle |
||||||
|
} |
||||||
|
|
||||||
|
// NewCheckpointOracle binds checkpoint contract and returns a registrar instance.
|
||||||
|
func NewCheckpointOracle(contractAddr common.Address, backend bind.ContractBackend) (*CheckpointOracle, error) { |
||||||
|
c, err := contract.NewCheckpointOracle(contractAddr, backend) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &CheckpointOracle{contract: c}, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Contract returns the underlying contract instance.
|
||||||
|
func (oracle *CheckpointOracle) Contract() *contract.CheckpointOracle { |
||||||
|
return oracle.contract |
||||||
|
} |
||||||
|
|
||||||
|
// LookupCheckpointEvents searches checkpoint event for specific section in the
|
||||||
|
// given log batches.
|
||||||
|
func (oracle *CheckpointOracle) LookupCheckpointEvents(blockLogs [][]*types.Log, section uint64, hash common.Hash) []*contract.CheckpointOracleNewCheckpointVote { |
||||||
|
var votes []*contract.CheckpointOracleNewCheckpointVote |
||||||
|
|
||||||
|
for _, logs := range blockLogs { |
||||||
|
for _, log := range logs { |
||||||
|
event, err := oracle.contract.ParseNewCheckpointVote(*log) |
||||||
|
if err != nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
if event.Index == section && common.Hash(event.CheckpointHash) == hash { |
||||||
|
votes = append(votes, event) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return votes |
||||||
|
} |
||||||
|
|
||||||
|
// RegisterCheckpoint registers the checkpoint with a batch of associated signatures
|
||||||
|
// that are collected off-chain and sorted by lexicographical order.
|
||||||
|
//
|
||||||
|
// Notably all signatures given should be transformed to "ethereum style" which transforms
|
||||||
|
// v from 0/1 to 27/28 according to the yellow paper.
|
||||||
|
func (oracle *CheckpointOracle) RegisterCheckpoint(key *ecdsa.PrivateKey, index uint64, hash []byte, rnum *big.Int, rhash [32]byte, sigs [][]byte) (*types.Transaction, error) { |
||||||
|
var ( |
||||||
|
r [][32]byte |
||||||
|
s [][32]byte |
||||||
|
v []uint8 |
||||||
|
) |
||||||
|
for i := 0; i < len(sigs); i++ { |
||||||
|
if len(sigs[i]) != 65 { |
||||||
|
return nil, errors.New("invalid signature") |
||||||
|
} |
||||||
|
r = append(r, common.BytesToHash(sigs[i][:32])) |
||||||
|
s = append(s, common.BytesToHash(sigs[i][32:64])) |
||||||
|
v = append(v, sigs[i][64]) |
||||||
|
} |
||||||
|
return oracle.contract.SetCheckpoint(bind.NewKeyedTransactor(key), rnum, rhash, common.BytesToHash(hash), index, v, r, s) |
||||||
|
} |
@ -0,0 +1,333 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package checkpointoracle |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"crypto/ecdsa" |
||||||
|
"encoding/binary" |
||||||
|
"errors" |
||||||
|
"math/big" |
||||||
|
"reflect" |
||||||
|
"sort" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract" |
||||||
|
"github.com/ethereum/go-ethereum/core" |
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/params" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
emptyHash = [32]byte{} |
||||||
|
|
||||||
|
checkpoint0 = params.TrustedCheckpoint{ |
||||||
|
SectionIndex: 0, |
||||||
|
SectionHead: common.HexToHash("0x7fa3c32f996c2bfb41a1a65b3d8ea3e0a33a1674cde43678ad6f4235e764d17d"), |
||||||
|
CHTRoot: common.HexToHash("0x98fc5d3de23a0fecebad236f6655533c157d26a1aedcd0852a514dc1169e6350"), |
||||||
|
BloomRoot: common.HexToHash("0x99b5adb52b337fe25e74c1c6d3835b896bd638611b3aebddb2317cce27a3f9fa"), |
||||||
|
} |
||||||
|
checkpoint1 = params.TrustedCheckpoint{ |
||||||
|
SectionIndex: 1, |
||||||
|
SectionHead: common.HexToHash("0x2d4dee68102125e59b0cc61b176bd89f0d12b3b91cfaf52ef8c2c82fb920c2d2"), |
||||||
|
CHTRoot: common.HexToHash("0x7d428008ece3b4c4ef5439f071930aad0bb75108d381308df73beadcd01ded95"), |
||||||
|
BloomRoot: common.HexToHash("0x652571f7736de17e7bbb427ac881474da684c6988a88bf51b10cca9a2ee148f4"), |
||||||
|
} |
||||||
|
checkpoint2 = params.TrustedCheckpoint{ |
||||||
|
SectionIndex: 2, |
||||||
|
SectionHead: common.HexToHash("0x61c0de578c0115b1dff8ef39aa600588c7c6ecb8a2f102003d7cf4c4146e9291"), |
||||||
|
CHTRoot: common.HexToHash("0x407a08a407a2bc3838b74ca3eb206903c9c8a186ccf5ef14af07794efff1970b"), |
||||||
|
BloomRoot: common.HexToHash("0x058b4161f558ce295a92925efc57f34f9210d5a30088d7475c183e0d3e58f5ac"), |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// The block frequency for creating checkpoint(only used in test)
|
||||||
|
sectionSize = big.NewInt(512) |
||||||
|
|
||||||
|
// The number of confirmations needed to generate a checkpoint(only used in test).
|
||||||
|
processConfirms = big.NewInt(4) |
||||||
|
) |
||||||
|
|
||||||
|
// validateOperation executes the operation, watches and delivers all events fired by the backend and ensures the
|
||||||
|
// correctness by assert function.
|
||||||
|
func validateOperation(t *testing.T, c *contract.CheckpointOracle, backend *backends.SimulatedBackend, operation func(), |
||||||
|
assert func(<-chan *contract.CheckpointOracleNewCheckpointVote) error, opName string) { |
||||||
|
// Watch all events and deliver them to assert function
|
||||||
|
var ( |
||||||
|
sink = make(chan *contract.CheckpointOracleNewCheckpointVote) |
||||||
|
sub, _ = c.WatchNewCheckpointVote(nil, sink, nil) |
||||||
|
) |
||||||
|
defer func() { |
||||||
|
// Close all subscribers
|
||||||
|
sub.Unsubscribe() |
||||||
|
}() |
||||||
|
operation() |
||||||
|
|
||||||
|
// flush pending block
|
||||||
|
backend.Commit() |
||||||
|
if err := assert(sink); err != nil { |
||||||
|
t.Errorf("operation {%s} failed, err %s", opName, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// validateEvents checks that the correct number of contract events
|
||||||
|
// fired by contract backend.
|
||||||
|
func validateEvents(target int, sink interface{}) (bool, []reflect.Value) { |
||||||
|
chanval := reflect.ValueOf(sink) |
||||||
|
chantyp := chanval.Type() |
||||||
|
if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.RecvDir == 0 { |
||||||
|
return false, nil |
||||||
|
} |
||||||
|
count := 0 |
||||||
|
var recv []reflect.Value |
||||||
|
timeout := time.After(1 * time.Second) |
||||||
|
cases := []reflect.SelectCase{{Chan: chanval, Dir: reflect.SelectRecv}, {Chan: reflect.ValueOf(timeout), Dir: reflect.SelectRecv}} |
||||||
|
for { |
||||||
|
chose, v, _ := reflect.Select(cases) |
||||||
|
if chose == 1 { |
||||||
|
// Not enough event received
|
||||||
|
return false, nil |
||||||
|
} |
||||||
|
count += 1 |
||||||
|
recv = append(recv, v) |
||||||
|
if count == target { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
done := time.After(50 * time.Millisecond) |
||||||
|
cases = cases[:1] |
||||||
|
cases = append(cases, reflect.SelectCase{Chan: reflect.ValueOf(done), Dir: reflect.SelectRecv}) |
||||||
|
chose, _, _ := reflect.Select(cases) |
||||||
|
// If chose equal 0, it means receiving redundant events.
|
||||||
|
return chose == 1, recv |
||||||
|
} |
||||||
|
|
||||||
|
func signCheckpoint(addr common.Address, privateKey *ecdsa.PrivateKey, index uint64, hash common.Hash) []byte { |
||||||
|
// EIP 191 style signatures
|
||||||
|
//
|
||||||
|
// Arguments when calculating hash to validate
|
||||||
|
// 1: byte(0x19) - the initial 0x19 byte
|
||||||
|
// 2: byte(0) - the version byte (data with intended validator)
|
||||||
|
// 3: this - the validator address
|
||||||
|
// -- Application specific data
|
||||||
|
// 4 : checkpoint section_index(uint64)
|
||||||
|
// 5 : checkpoint hash (bytes32)
|
||||||
|
// hash = keccak256(checkpoint_index, section_head, cht_root, bloom_root)
|
||||||
|
buf := make([]byte, 8) |
||||||
|
binary.BigEndian.PutUint64(buf, index) |
||||||
|
data := append([]byte{0x19, 0x00}, append(addr.Bytes(), append(buf, hash.Bytes()...)...)...) |
||||||
|
sig, _ := crypto.Sign(crypto.Keccak256(data), privateKey) |
||||||
|
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||||
|
return sig |
||||||
|
} |
||||||
|
|
||||||
|
// assertSignature verifies whether the recovered signers are equal with expected.
|
||||||
|
func assertSignature(addr common.Address, index uint64, hash [32]byte, r, s [32]byte, v uint8, expect common.Address) bool { |
||||||
|
buf := make([]byte, 8) |
||||||
|
binary.BigEndian.PutUint64(buf, index) |
||||||
|
data := append([]byte{0x19, 0x00}, append(addr.Bytes(), append(buf, hash[:]...)...)...) |
||||||
|
pubkey, err := crypto.Ecrecover(crypto.Keccak256(data), append(r[:], append(s[:], v-27)...)) |
||||||
|
if err != nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
var signer common.Address |
||||||
|
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) |
||||||
|
return bytes.Equal(signer.Bytes(), expect.Bytes()) |
||||||
|
} |
||||||
|
|
||||||
|
type Account struct { |
||||||
|
key *ecdsa.PrivateKey |
||||||
|
addr common.Address |
||||||
|
} |
||||||
|
type Accounts []Account |
||||||
|
|
||||||
|
func (a Accounts) Len() int { return len(a) } |
||||||
|
func (a Accounts) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
||||||
|
func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 } |
||||||
|
|
||||||
|
func TestCheckpointRegister(t *testing.T) { |
||||||
|
// Initialize test accounts
|
||||||
|
var accounts Accounts |
||||||
|
for i := 0; i < 3; i++ { |
||||||
|
key, _ := crypto.GenerateKey() |
||||||
|
addr := crypto.PubkeyToAddress(key.PublicKey) |
||||||
|
accounts = append(accounts, Account{key: key, addr: addr}) |
||||||
|
} |
||||||
|
sort.Sort(accounts) |
||||||
|
|
||||||
|
// Deploy registrar contract
|
||||||
|
transactOpts := bind.NewKeyedTransactor(accounts[0].key) |
||||||
|
contractBackend := backends.NewSimulatedBackend(core.GenesisAlloc{accounts[0].addr: {Balance: big.NewInt(1000000000)}, accounts[1].addr: {Balance: big.NewInt(1000000000)}, accounts[2].addr: {Balance: big.NewInt(1000000000)}}, 10000000) |
||||||
|
// 3 trusted signers, threshold 2
|
||||||
|
contractAddr, _, c, err := contract.DeployCheckpointOracle(transactOpts, contractBackend, []common.Address{accounts[0].addr, accounts[1].addr, accounts[2].addr}, sectionSize, processConfirms, big.NewInt(2)) |
||||||
|
if err != nil { |
||||||
|
t.Error("Failed to deploy registrar contract", err) |
||||||
|
} |
||||||
|
contractBackend.Commit() |
||||||
|
|
||||||
|
// getRecent returns block height and hash of the head parent.
|
||||||
|
getRecent := func() (*big.Int, common.Hash) { |
||||||
|
parentNumber := new(big.Int).Sub(contractBackend.Blockchain().CurrentHeader().Number, big.NewInt(1)) |
||||||
|
parentHash := contractBackend.Blockchain().CurrentHeader().ParentHash |
||||||
|
return parentNumber, parentHash |
||||||
|
} |
||||||
|
// collectSig generates specified number signatures.
|
||||||
|
collectSig := func(index uint64, hash common.Hash, n int, unauthorized *ecdsa.PrivateKey) (v []uint8, r [][32]byte, s [][32]byte) { |
||||||
|
for i := 0; i < n; i++ { |
||||||
|
sig := signCheckpoint(contractAddr, accounts[i].key, index, hash) |
||||||
|
if unauthorized != nil { |
||||||
|
sig = signCheckpoint(contractAddr, unauthorized, index, hash) |
||||||
|
} |
||||||
|
r = append(r, common.BytesToHash(sig[:32])) |
||||||
|
s = append(s, common.BytesToHash(sig[32:64])) |
||||||
|
v = append(v, sig[64]) |
||||||
|
} |
||||||
|
return v, r, s |
||||||
|
} |
||||||
|
// insertEmptyBlocks inserts a batch of empty blocks to blockchain.
|
||||||
|
insertEmptyBlocks := func(number int) { |
||||||
|
for i := 0; i < number; i++ { |
||||||
|
contractBackend.Commit() |
||||||
|
} |
||||||
|
} |
||||||
|
// assert checks whether the current contract status is same with
|
||||||
|
// the expected.
|
||||||
|
assert := func(index uint64, hash [32]byte, height *big.Int) error { |
||||||
|
lindex, lhash, lheight, err := c.GetLatestCheckpoint(nil) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if lindex != index { |
||||||
|
return errors.New("latest checkpoint index mismatch") |
||||||
|
} |
||||||
|
if !bytes.Equal(lhash[:], hash[:]) { |
||||||
|
return errors.New("latest checkpoint hash mismatch") |
||||||
|
} |
||||||
|
if lheight.Cmp(height) != 0 { |
||||||
|
return errors.New("latest checkpoint height mismatch") |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Test future checkpoint registration
|
||||||
|
validateOperation(t, c, contractBackend, func() { |
||||||
|
number, hash := getRecent() |
||||||
|
v, r, s := collectSig(0, checkpoint0.Hash(), 2, nil) |
||||||
|
c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s) |
||||||
|
}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { |
||||||
|
return assert(0, emptyHash, big.NewInt(0)) |
||||||
|
}, "test future checkpoint registration") |
||||||
|
|
||||||
|
insertEmptyBlocks(int(sectionSize.Uint64() + processConfirms.Uint64())) |
||||||
|
|
||||||
|
// Test transaction replay protection
|
||||||
|
validateOperation(t, c, contractBackend, func() { |
||||||
|
number, hash := getRecent() |
||||||
|
v, r, s := collectSig(0, checkpoint0.Hash(), 2, nil) |
||||||
|
hash = common.HexToHash("deadbeef") |
||||||
|
c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s) |
||||||
|
}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { |
||||||
|
return assert(0, emptyHash, big.NewInt(0)) |
||||||
|
}, "test transaction replay protection") |
||||||
|
|
||||||
|
// Test unauthorized signature checking
|
||||||
|
validateOperation(t, c, contractBackend, func() { |
||||||
|
number, hash := getRecent() |
||||||
|
u, _ := crypto.GenerateKey() |
||||||
|
v, r, s := collectSig(0, checkpoint0.Hash(), 2, u) |
||||||
|
c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s) |
||||||
|
}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { |
||||||
|
return assert(0, emptyHash, big.NewInt(0)) |
||||||
|
}, "test unauthorized signature checking") |
||||||
|
|
||||||
|
// Test un-multi-signature checkpoint registration
|
||||||
|
validateOperation(t, c, contractBackend, func() { |
||||||
|
number, hash := getRecent() |
||||||
|
v, r, s := collectSig(0, checkpoint0.Hash(), 1, nil) |
||||||
|
c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s) |
||||||
|
}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { |
||||||
|
return assert(0, emptyHash, big.NewInt(0)) |
||||||
|
}, "test un-multi-signature checkpoint registration") |
||||||
|
|
||||||
|
// Test valid checkpoint registration
|
||||||
|
validateOperation(t, c, contractBackend, func() { |
||||||
|
number, hash := getRecent() |
||||||
|
v, r, s := collectSig(0, checkpoint0.Hash(), 2, nil) |
||||||
|
c.SetCheckpoint(transactOpts, number, hash, checkpoint0.Hash(), 0, v, r, s) |
||||||
|
}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { |
||||||
|
if valid, recv := validateEvents(2, events); !valid { |
||||||
|
return errors.New("receive incorrect number of events") |
||||||
|
} else { |
||||||
|
for i := 0; i < len(recv); i++ { |
||||||
|
event := recv[i].Interface().(*contract.CheckpointOracleNewCheckpointVote) |
||||||
|
if !assertSignature(contractAddr, event.Index, event.CheckpointHash, event.R, event.S, event.V, accounts[i].addr) { |
||||||
|
return errors.New("recover signer failed") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
number, _ := getRecent() |
||||||
|
return assert(0, checkpoint0.Hash(), number.Add(number, big.NewInt(1))) |
||||||
|
}, "test valid checkpoint registration") |
||||||
|
|
||||||
|
distance := 3*sectionSize.Uint64() + processConfirms.Uint64() - contractBackend.Blockchain().CurrentHeader().Number.Uint64() |
||||||
|
insertEmptyBlocks(int(distance)) |
||||||
|
|
||||||
|
// Test uncontinuous checkpoint registration
|
||||||
|
validateOperation(t, c, contractBackend, func() { |
||||||
|
number, hash := getRecent() |
||||||
|
v, r, s := collectSig(2, checkpoint2.Hash(), 2, nil) |
||||||
|
c.SetCheckpoint(transactOpts, number, hash, checkpoint2.Hash(), 2, v, r, s) |
||||||
|
}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { |
||||||
|
if valid, recv := validateEvents(2, events); !valid { |
||||||
|
return errors.New("receive incorrect number of events") |
||||||
|
} else { |
||||||
|
for i := 0; i < len(recv); i++ { |
||||||
|
event := recv[i].Interface().(*contract.CheckpointOracleNewCheckpointVote) |
||||||
|
if !assertSignature(contractAddr, event.Index, event.CheckpointHash, event.R, event.S, event.V, accounts[i].addr) { |
||||||
|
return errors.New("recover signer failed") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
number, _ := getRecent() |
||||||
|
return assert(2, checkpoint2.Hash(), number.Add(number, big.NewInt(1))) |
||||||
|
}, "test uncontinuous checkpoint registration") |
||||||
|
|
||||||
|
// Test old checkpoint registration
|
||||||
|
validateOperation(t, c, contractBackend, func() { |
||||||
|
number, hash := getRecent() |
||||||
|
v, r, s := collectSig(1, checkpoint1.Hash(), 2, nil) |
||||||
|
c.SetCheckpoint(transactOpts, number, hash, checkpoint1.Hash(), 1, v, r, s) |
||||||
|
}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { |
||||||
|
number, _ := getRecent() |
||||||
|
return assert(2, checkpoint2.Hash(), number) |
||||||
|
}, "test uncontinuous checkpoint registration") |
||||||
|
|
||||||
|
// Test stale checkpoint registration
|
||||||
|
validateOperation(t, c, contractBackend, func() { |
||||||
|
number, hash := getRecent() |
||||||
|
v, r, s := collectSig(2, checkpoint2.Hash(), 2, nil) |
||||||
|
c.SetCheckpoint(transactOpts, number, hash, checkpoint2.Hash(), 2, v, r, s) |
||||||
|
}, func(events <-chan *contract.CheckpointOracleNewCheckpointVote) error { |
||||||
|
number, _ := getRecent() |
||||||
|
return assert(2, checkpoint2.Hash(), number.Sub(number, big.NewInt(1))) |
||||||
|
}, "test stale checkpoint registration") |
||||||
|
} |
@ -0,0 +1,158 @@ |
|||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package les |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/binary" |
||||||
|
"sync/atomic" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/contracts/checkpointoracle" |
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/log" |
||||||
|
"github.com/ethereum/go-ethereum/params" |
||||||
|
) |
||||||
|
|
||||||
|
// checkpointOracle is responsible for offering the latest stable checkpoint
|
||||||
|
// generated and announced by the contract admins on-chain. The checkpoint is
|
||||||
|
// verified by clients locally during the checkpoint syncing.
|
||||||
|
type checkpointOracle struct { |
||||||
|
config *params.CheckpointOracleConfig |
||||||
|
contract *checkpointoracle.CheckpointOracle |
||||||
|
|
||||||
|
// Whether the contract backend is set.
|
||||||
|
running int32 |
||||||
|
|
||||||
|
getLocal func(uint64) params.TrustedCheckpoint // Function used to retrieve local checkpoint
|
||||||
|
syncDoneHook func() // Function used to notify that light syncing has completed.
|
||||||
|
} |
||||||
|
|
||||||
|
// newCheckpointOracle returns a checkpoint registrar handler.
|
||||||
|
func newCheckpointOracle(config *params.CheckpointOracleConfig, getLocal func(uint64) params.TrustedCheckpoint) *checkpointOracle { |
||||||
|
if config == nil { |
||||||
|
log.Info("Checkpoint registrar is not enabled") |
||||||
|
return nil |
||||||
|
} |
||||||
|
if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold { |
||||||
|
log.Warn("Invalid checkpoint registrar config") |
||||||
|
return nil |
||||||
|
} |
||||||
|
log.Info("Configured checkpoint registrar", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold) |
||||||
|
|
||||||
|
return &checkpointOracle{ |
||||||
|
config: config, |
||||||
|
getLocal: getLocal, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// start binds the registrar contract and start listening to the
|
||||||
|
// newCheckpointEvent for the server side.
|
||||||
|
func (reg *checkpointOracle) start(backend bind.ContractBackend) { |
||||||
|
contract, err := checkpointoracle.NewCheckpointOracle(reg.config.Address, backend) |
||||||
|
if err != nil { |
||||||
|
log.Error("Oracle contract binding failed", "err", err) |
||||||
|
return |
||||||
|
} |
||||||
|
if !atomic.CompareAndSwapInt32(®.running, 0, 1) { |
||||||
|
log.Error("Already bound and listening to registrar") |
||||||
|
return |
||||||
|
} |
||||||
|
reg.contract = contract |
||||||
|
} |
||||||
|
|
||||||
|
// isRunning returns an indicator whether the registrar is running.
|
||||||
|
func (reg *checkpointOracle) isRunning() bool { |
||||||
|
return atomic.LoadInt32(®.running) == 1 |
||||||
|
} |
||||||
|
|
||||||
|
// stableCheckpoint returns the stable checkpoint which was generated by local
|
||||||
|
// indexers and announced by trusted signers.
|
||||||
|
func (reg *checkpointOracle) stableCheckpoint() (*params.TrustedCheckpoint, uint64) { |
||||||
|
// Retrieve the latest checkpoint from the contract, abort if empty
|
||||||
|
latest, hash, height, err := reg.contract.Contract().GetLatestCheckpoint(nil) |
||||||
|
if err != nil || (latest == 0 && hash == [32]byte{}) { |
||||||
|
return nil, 0 |
||||||
|
} |
||||||
|
local := reg.getLocal(latest) |
||||||
|
|
||||||
|
// The following scenarios may occur:
|
||||||
|
//
|
||||||
|
// * local node is out of sync so that it doesn't have the
|
||||||
|
// checkpoint which registered in the contract.
|
||||||
|
// * local checkpoint doesn't match with the registered one.
|
||||||
|
//
|
||||||
|
// In both cases, server won't send the **stable** checkpoint
|
||||||
|
// to the client(no worry, client can use hardcoded one instead).
|
||||||
|
if local.HashEqual(common.Hash(hash)) { |
||||||
|
return &local, height.Uint64() |
||||||
|
} |
||||||
|
return nil, 0 |
||||||
|
} |
||||||
|
|
||||||
|
// verifySigners recovers the signer addresses according to the signature and
|
||||||
|
// checks whether there are enough approvals to finalize the checkpoint.
|
||||||
|
func (reg *checkpointOracle) verifySigners(index uint64, hash [32]byte, signatures [][]byte) (bool, []common.Address) { |
||||||
|
// Short circuit if the given signatures doesn't reach the threshold.
|
||||||
|
if len(signatures) < int(reg.config.Threshold) { |
||||||
|
return false, nil |
||||||
|
} |
||||||
|
var ( |
||||||
|
signers []common.Address |
||||||
|
checked = make(map[common.Address]struct{}) |
||||||
|
) |
||||||
|
for i := 0; i < len(signatures); i++ { |
||||||
|
if len(signatures[i]) != 65 { |
||||||
|
continue |
||||||
|
} |
||||||
|
// EIP 191 style signatures
|
||||||
|
//
|
||||||
|
// Arguments when calculating hash to validate
|
||||||
|
// 1: byte(0x19) - the initial 0x19 byte
|
||||||
|
// 2: byte(0) - the version byte (data with intended validator)
|
||||||
|
// 3: this - the validator address
|
||||||
|
// -- Application specific data
|
||||||
|
// 4 : checkpoint section_index (uint64)
|
||||||
|
// 5 : checkpoint hash (bytes32)
|
||||||
|
// hash = keccak256(checkpoint_index, section_head, cht_root, bloom_root)
|
||||||
|
buf := make([]byte, 8) |
||||||
|
binary.BigEndian.PutUint64(buf, index) |
||||||
|
data := append([]byte{0x19, 0x00}, append(reg.config.Address.Bytes(), append(buf, hash[:]...)...)...) |
||||||
|
signatures[i][64] -= 27 // Transform V from 27/28 to 0/1 according to the yellow paper for verification.
|
||||||
|
pubkey, err := crypto.Ecrecover(crypto.Keccak256(data), signatures[i]) |
||||||
|
if err != nil { |
||||||
|
return false, nil |
||||||
|
} |
||||||
|
var signer common.Address |
||||||
|
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) |
||||||
|
if _, exist := checked[signer]; exist { |
||||||
|
continue |
||||||
|
} |
||||||
|
for _, s := range reg.config.Signers { |
||||||
|
if s == signer { |
||||||
|
signers = append(signers, signer) |
||||||
|
checked[signer] = struct{}{} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
threshold := reg.config.Threshold |
||||||
|
if uint64(len(signers)) < threshold { |
||||||
|
log.Warn("Not enough signers to approve checkpoint", "signers", len(signers), "threshold", threshold) |
||||||
|
return false, nil |
||||||
|
} |
||||||
|
return true, signers |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package les |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"math/big" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core" |
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/light" |
||||||
|
"github.com/ethereum/go-ethereum/params" |
||||||
|
) |
||||||
|
|
||||||
|
// Test light syncing which will download all headers from genesis.
|
||||||
|
func TestLightSyncingLes2(t *testing.T) { testCheckpointSyncing(t, 2, 0) } |
||||||
|
func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 0) } |
||||||
|
|
||||||
|
// Test legacy checkpoint syncing which will download tail headers
|
||||||
|
// based on a hardcoded checkpoint.
|
||||||
|
func TestLegacyCheckpointSyncingLes2(t *testing.T) { testCheckpointSyncing(t, 2, 1) } |
||||||
|
func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 1) } |
||||||
|
|
||||||
|
// Test checkpoint syncing which will download tail headers based
|
||||||
|
// on a verified checkpoint.
|
||||||
|
func TestCheckpointSyncingLes2(t *testing.T) { testCheckpointSyncing(t, 2, 2) } |
||||||
|
func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, 3, 2) } |
||||||
|
|
||||||
|
func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) { |
||||||
|
config := light.TestServerIndexerConfig |
||||||
|
|
||||||
|
waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { |
||||||
|
for { |
||||||
|
cs, _, _ := cIndexer.Sections() |
||||||
|
bts, _, _ := btIndexer.Sections() |
||||||
|
if cs >= 1 && bts >= 1 { |
||||||
|
break |
||||||
|
} |
||||||
|
time.Sleep(10 * time.Millisecond) |
||||||
|
} |
||||||
|
} |
||||||
|
// Generate 512+4 blocks (totally 1 CHT sections)
|
||||||
|
server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, false) |
||||||
|
defer tearDown() |
||||||
|
|
||||||
|
expected := config.ChtSize + config.ChtConfirms |
||||||
|
|
||||||
|
// Checkpoint syncing or legacy checkpoint syncing.
|
||||||
|
if syncMode == 1 || syncMode == 2 { |
||||||
|
// Assemble checkpoint 0
|
||||||
|
s, _, head := server.chtIndexer.Sections() |
||||||
|
cp := ¶ms.TrustedCheckpoint{ |
||||||
|
SectionIndex: 0, |
||||||
|
SectionHead: head, |
||||||
|
CHTRoot: light.GetChtRoot(server.db, s-1, head), |
||||||
|
BloomRoot: light.GetBloomTrieRoot(server.db, s-1, head), |
||||||
|
} |
||||||
|
if syncMode == 1 { |
||||||
|
// Register the assembled checkpoint as hardcoded one.
|
||||||
|
client.pm.checkpoint = cp |
||||||
|
client.pm.blockchain.(*light.LightChain).AddTrustedCheckpoint(cp) |
||||||
|
} else { |
||||||
|
// Register the assembled checkpoint into oracle.
|
||||||
|
header := server.backend.Blockchain().CurrentHeader() |
||||||
|
|
||||||
|
data := append([]byte{0x19, 0x00}, append(registrarAddr.Bytes(), append([]byte{0, 0, 0, 0, 0, 0, 0, 0}, cp.Hash().Bytes()...)...)...) |
||||||
|
sig, _ := crypto.Sign(crypto.Keccak256(data), signerKey) |
||||||
|
sig[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||||
|
if _, err := server.pm.reg.contract.RegisterCheckpoint(signerKey, cp.SectionIndex, cp.Hash().Bytes(), new(big.Int).Sub(header.Number, big.NewInt(1)), header.ParentHash, [][]byte{sig}); err != nil { |
||||||
|
t.Error("register checkpoint failed", err) |
||||||
|
} |
||||||
|
server.backend.Commit() |
||||||
|
|
||||||
|
// Wait for the checkpoint registration
|
||||||
|
for { |
||||||
|
_, hash, _, err := server.pm.reg.contract.Contract().GetLatestCheckpoint(nil) |
||||||
|
if err != nil || hash == [32]byte{} { |
||||||
|
time.Sleep(100 * time.Millisecond) |
||||||
|
continue |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
expected += 1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
done := make(chan error) |
||||||
|
client.pm.reg.syncDoneHook = func() { |
||||||
|
header := client.pm.blockchain.CurrentHeader() |
||||||
|
if header.Number.Uint64() == expected { |
||||||
|
done <- nil |
||||||
|
} else { |
||||||
|
done <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expected, header.Number) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Create connected peer pair.
|
||||||
|
peer, err1, lPeer, err2 := newTestPeerPair("peer", protocol, server.pm, client.pm) |
||||||
|
select { |
||||||
|
case <-time.After(time.Millisecond * 100): |
||||||
|
case err := <-err1: |
||||||
|
t.Fatalf("peer 1 handshake error: %v", err) |
||||||
|
case err := <-err2: |
||||||
|
t.Fatalf("peer 2 handshake error: %v", err) |
||||||
|
} |
||||||
|
server.rPeer, client.rPeer = peer, lPeer |
||||||
|
|
||||||
|
select { |
||||||
|
case err := <-done: |
||||||
|
if err != nil { |
||||||
|
t.Error("sync failed", err) |
||||||
|
} |
||||||
|
return |
||||||
|
case <-time.NewTimer(10 * time.Second).C: |
||||||
|
t.Error("checkpoint syncing timeout") |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue