mirror of https://github.com/ethereum/go-ethereum
all: remove notion of trusted checkpoints in the post-merge world (#27147)
* all: remove notion of trusted checkpoints in the post-merge world * light: remove unused function * eth/ethconfig, les: remove unused config option * les: make linter happy --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>pull/27154/head
parent
d3ece3a07c
commit
1e556d220c
@ -1,103 +0,0 @@ |
||||
## Checkpoint-admin |
||||
|
||||
Checkpoint-admin is a tool for updating checkpoint oracle status. It provides a series of functions including deploying checkpoint oracle contract, signing for new checkpoints, and updating checkpoints in the checkpoint oracle contract. |
||||
|
||||
### Checkpoint |
||||
|
||||
In the LES protocol, there is an important concept called checkpoint. In simple terms, whenever a certain number of blocks are generated on the blockchain, a new checkpoint is generated which contains some important information such as |
||||
|
||||
* Block hash at checkpoint |
||||
* Canonical hash trie root at checkpoint |
||||
* Bloom trie root at checkpoint |
||||
|
||||
*For a more detailed introduction to checkpoint, please see the LES [spec](https://github.com/ethereum/devp2p/blob/master/caps/les.md).* |
||||
|
||||
Using this information, light clients can skip all historical block headers when synchronizing data and start synchronization from this checkpoint. Therefore, as long as the light client can obtain some latest and correct checkpoints, the amount of data and time for synchronization will be greatly reduced. |
||||
|
||||
However, from a security perspective, the most critical step in a synchronization algorithm based on checkpoints is to determine whether the checkpoint used by the light client is correct. Otherwise, all blockchain data synchronized based on this checkpoint may be wrong. For this we provide two different ways to ensure the correctness of the checkpoint used by the light client. |
||||
|
||||
#### Hardcoded checkpoint |
||||
|
||||
There are several hardcoded checkpoints in the [source code](https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L38) of the go-ethereum project. These checkpoints are updated by go-ethereum developers when new versions of software are released. Because light client users trust Geth developers to some extent, hardcoded checkpoints in the code can also be considered correct. |
||||
|
||||
#### Checkpoint oracle |
||||
|
||||
Hardcoded checkpoints can solve the problem of verifying the correctness of checkpoints (although this is a more centralized solution). But the pain point of this solution is that developers can only update checkpoints when a new version of software is released. In addition, light client users usually do not keep the Geth version they use always up to date. So hardcoded checkpoints used by users are generally stale. Therefore, it still needs to download a large amount of blockchain data during synchronization. |
||||
|
||||
Checkpoint oracle is a more flexible solution. In simple terms, this is a smart contract that is deployed on the blockchain. The smart contract records several designated trusted signers. Whenever enough trusted signers have issued their signatures for the same checkpoint, it can be considered that the checkpoint has been authenticated by the signers. Checkpoints authenticated by trusted signers can be considered correct. |
||||
|
||||
So this way, even without updating the software version, as long as the trusted signers regularly update the checkpoint in oracle on time, the light client can always use the latest and verified checkpoint for data synchronization. |
||||
|
||||
### Usage |
||||
|
||||
Checkpoint-admin is a command line tool designed for checkpoint oracle. Users can easily deploy contracts and update checkpoints through this tool. |
||||
|
||||
#### Install |
||||
|
||||
```shell |
||||
go get github.com/ethereum/go-ethereum/cmd/checkpoint-admin |
||||
``` |
||||
|
||||
#### Deploy |
||||
|
||||
Deploy checkpoint oracle contract. `--signers` indicates the specified trusted signer, and `--threshold` indicates the minimum number of signatures required by trusted signers to update a checkpoint. |
||||
|
||||
```shell |
||||
checkpoint-admin deploy --rpc <NODE_RPC_ENDPOINT> --clef <CLEF_ENDPOINT> --signer <SIGNER_TO_SIGN_TX> --signers <TRUSTED_SIGNER_LIST> --threshold 1 |
||||
``` |
||||
|
||||
It is worth noting that checkpoint-admin only supports clef as a signer for transactions and plain text(checkpoint). For more clef usage, please see the clef [tutorial](https://geth.ethereum.org/docs/tools/clef/tutorial) . |
||||
|
||||
#### Sign |
||||
|
||||
Checkpoint-admin provides two different modes of signing. You can automatically obtain the current stable checkpoint and sign it interactively, and you can also use the information provided by the command line flags to sign checkpoint offline. |
||||
|
||||
**Interactive mode** |
||||
|
||||
```shell |
||||
checkpoint-admin sign --clef <CLEF_ENDPOINT> --signer <SIGNER_TO_SIGN_CHECKPOINT> --rpc <NODE_RPC_ENDPOINT> |
||||
``` |
||||
|
||||
*It is worth noting that the connected Geth node can be a fullnode or a light client. If it is fullnode, you must enable the LES protocol. E.G. add `--light.serv 50` to the startup command line flags*. |
||||
|
||||
**Offline mode** |
||||
|
||||
```shell |
||||
checkpoint-admin sign --clef <CLEF_ENDPOINT> --signer <SIGNER_TO_SIGN_CHECKPOINT> --index <CHECKPOINT_INDEX> --hash <CHECKPOINT_HASH> --oracle <CHECKPOINT_ORACLE_ADDRESS> |
||||
``` |
||||
|
||||
*CHECKPOINT_HASH is obtained based on this [calculation method](https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L251).* |
||||
|
||||
#### Publish |
||||
|
||||
Collect enough signatures from different trusted signers for the same checkpoint and submit them to oracle to update the "authenticated" checkpoint in the contract. |
||||
|
||||
```shell |
||||
checkpoint-admin publish --clef <CLEF_ENDPOINT> --rpc <NODE_RPC_ENDPOINT> --signer <SIGNER_TO_SIGN_TX> --index <CHECKPOINT_INDEX> --signatures <CHECKPOINT_SIGNATURE_LIST> |
||||
``` |
||||
|
||||
#### Status query |
||||
|
||||
Check the latest status of checkpoint oracle. |
||||
|
||||
```shell |
||||
checkpoint-admin status --rpc <NODE_RPC_ENDPOINT> |
||||
``` |
||||
|
||||
### Enable checkpoint oracle in your private network |
||||
|
||||
Currently, only the Ethereum mainnet and the default supported test networks (rinkeby, goerli) activate this feature. If you want to activate this feature in your private network, you can overwrite the relevant checkpoint oracle settings through the configuration file after deploying the oracle contract. |
||||
|
||||
* Get your node configuration file `geth dumpconfig OTHER_COMMAND_LINE_OPTIONS > config.toml` |
||||
* Edit the configuration file and add the following information |
||||
|
||||
```toml |
||||
[Eth.CheckpointOracle] |
||||
Address = CHECKPOINT_ORACLE_ADDRESS |
||||
Signers = [TRUSTED_SIGNER_1, ..., TRUSTED_SIGNER_N] |
||||
Threshold = THRESHOLD |
||||
``` |
||||
|
||||
* Start geth with the modified configuration file |
||||
|
||||
*In the private network, all fullnodes and light clients need to be started using the same checkpoint oracle settings.* |
@ -1,120 +0,0 @@ |
||||
// Copyright 2019 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 ( |
||||
"strconv" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts" |
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind" |
||||
"github.com/ethereum/go-ethereum/accounts/external" |
||||
"github.com/ethereum/go-ethereum/cmd/utils" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"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" |
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
// newClient creates a client with specified remote URL.
|
||||
func newClient(ctx *cli.Context) *ethclient.Client { |
||||
client, err := ethclient.Dial(ctx.String(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.IsSet(indexFlag.Name) { |
||||
var result [3]string |
||||
index := uint64(ctx.Int64(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 |
||||
} |
||||
|
||||
// newClefSigner sets up a clef backend and returns a clef transaction signer.
|
||||
func newClefSigner(ctx *cli.Context) *bind.TransactOpts { |
||||
clef, err := external.NewExternalSigner(ctx.String(clefURLFlag.Name)) |
||||
if err != nil { |
||||
utils.Fatalf("Failed to create clef signer %v", err) |
||||
} |
||||
return bind.NewClefTransactor(clef, accounts.Account{Address: common.HexToAddress(ctx.String(signerFlag.Name))}) |
||||
} |
@ -1,311 +0,0 @@ |
||||
// Copyright 2019 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/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" |
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
var commandDeploy = &cli.Command{ |
||||
Name: "deploy", |
||||
Usage: "Deploy a new checkpoint oracle contract", |
||||
Flags: []cli.Flag{ |
||||
nodeURLFlag, |
||||
clefURLFlag, |
||||
signerFlag, |
||||
signersFlag, |
||||
thresholdFlag, |
||||
}, |
||||
Action: deploy, |
||||
} |
||||
|
||||
var commandSign = &cli.Command{ |
||||
Name: "sign", |
||||
Usage: "Sign the checkpoint with the specified key", |
||||
Flags: []cli.Flag{ |
||||
nodeURLFlag, |
||||
clefURLFlag, |
||||
signerFlag, |
||||
indexFlag, |
||||
hashFlag, |
||||
oracleFlag, |
||||
}, |
||||
Action: sign, |
||||
} |
||||
|
||||
var commandPublish = &cli.Command{ |
||||
Name: "publish", |
||||
Usage: "Publish a checkpoint into the oracle", |
||||
Flags: []cli.Flag{ |
||||
nodeURLFlag, |
||||
clefURLFlag, |
||||
signerFlag, |
||||
indexFlag, |
||||
signaturesFlag, |
||||
}, |
||||
Action: 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) |
||||
|
||||
// setup clef signer, create an abigen transactor and an RPC client
|
||||
transactor, client := newClefSigner(ctx), newClient(ctx) |
||||
|
||||
// Deploy the checkpoint oracle
|
||||
fmt.Println("Sending deploy request to Clef...") |
||||
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.IsSet(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.String(nodeURLFlag.Name)) |
||||
|
||||
checkpoint := getCheckpoint(ctx, node) |
||||
chash, cindex, address = checkpoint.Hash(), checkpoint.SectionIndex, 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()) |
||||
|
||||
// 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.String(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()...)) |
||||
|
||||
fmt.Println("Sending signing request to Clef...") |
||||
if err := clef.Call(&signature, "account_signData", accounts.MimetypeDataWithValidator, signer, p); err != nil { |
||||
utils.Fatalf("Failed to sign checkpoint, err %v", err) |
||||
} |
||||
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.String(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
|
||||
fmt.Println("Sending publish request to Clef...") |
||||
tx, err := oracle.RegisterCheckpoint(newClefSigner(ctx), 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 |
||||
} |
@ -1,96 +0,0 @@ |
||||
// Copyright 2019 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/common/fdlimit" |
||||
"github.com/ethereum/go-ethereum/internal/flags" |
||||
"github.com/ethereum/go-ethereum/log" |
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
var app = flags.NewApp("ethereum checkpoint helper tool") |
||||
|
||||
func init() { |
||||
app.Commands = []*cli.Command{ |
||||
commandStatus, |
||||
commandDeploy, |
||||
commandSign, |
||||
commandPublish, |
||||
} |
||||
app.Flags = []cli.Flag{ |
||||
oracleFlag, |
||||
nodeURLFlag, |
||||
} |
||||
} |
||||
|
||||
// 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", |
||||
} |
||||
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 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) |
||||
} |
||||
} |
@ -1,60 +0,0 @@ |
||||
// Copyright 2019 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/common" |
||||
"github.com/urfave/cli/v2" |
||||
) |
||||
|
||||
var commandStatus = &cli.Command{ |
||||
Name: "status", |
||||
Usage: "Fetches the signers and checkpoint status of the oracle contract", |
||||
Flags: []cli.Flag{ |
||||
nodeURLFlag, |
||||
}, |
||||
Action: 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.String(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 |
||||
} |
@ -1,428 +0,0 @@ |
||||
// 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 |
||||
_ = bind.Bind |
||||
_ = common.Big1 |
||||
_ = types.BloomLookup |
||||
_ = event.NewSubscription |
||||
) |
||||
|
||||
// CheckpointOracleABI is the input ABI used to generate the binding from.
|
||||
const CheckpointOracleABI = "[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_adminlist\",\"type\":\"address[]\"},{\"internalType\":\"uint256\",\"name\":\"_sectionSize\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_processConfirms\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_threshold\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint64\",\"name\":\"index\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"checkpointHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"NewCheckpointVote\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"GetAllAdmin\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"GetLatestCheckpoint\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_recentNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_recentHash\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_hash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_sectionIndex\",\"type\":\"uint64\"},{\"internalType\":\"uint8[]\",\"name\":\"v\",\"type\":\"uint8[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"r\",\"type\":\"bytes32[]\"},{\"internalType\":\"bytes32[]\",\"name\":\"s\",\"type\":\"bytes32[]\"}],\"name\":\"SetCheckpoint\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" |
||||
|
||||
// CheckpointOracleFuncSigs maps the 4-byte function signature to its string representation.
|
||||
var CheckpointOracleFuncSigs = map[string]string{ |
||||
"45848dfc": "GetAllAdmin()", |
||||
"4d6a304c": "GetLatestCheckpoint()", |
||||
"d459fc46": "SetCheckpoint(uint256,bytes32,bytes32,uint64,uint8[],bytes32[],bytes32[])", |
||||
} |
||||
|
||||
// CheckpointOracleBin is the compiled bytecode used for deploying new contracts.
|
||||
var CheckpointOracleBin = "0x608060405234801561001057600080fd5b506040516108703803806108708339818101604052608081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825186602082028301116401000000008211171561008557600080fd5b82525081516020918201928201910280838360005b838110156100b257818101518382015260200161009a565b50505050919091016040908152602083015190830151606090930151909450919250600090505b84518110156101855760016000808784815181106100f357fe5b60200260200101516001600160a01b03166001600160a01b0316815260200190815260200160002060006101000a81548160ff021916908315150217905550600185828151811061014057fe5b60209081029190910181015182546001808201855560009485529290932090920180546001600160a01b0319166001600160a01b0390931692909217909155016100d9565b50600592909255600655600755506106ce806101a26000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806345848dfc146100465780634d6a304c1461009e578063d459fc46146100cf575b600080fd5b61004e6102b0565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561008a578181015183820152602001610072565b505050509050019250505060405180910390f35b6100a6610365565b6040805167ffffffffffffffff9094168452602084019290925282820152519081900360600190f35b61029c600480360360e08110156100e557600080fd5b81359160208101359160408201359167ffffffffffffffff6060820135169181019060a08101608082013564010000000081111561012257600080fd5b82018360208201111561013457600080fd5b8035906020019184602083028401116401000000008311171561015657600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101a657600080fd5b8201836020820111156101b857600080fd5b803590602001918460208302840111640100000000831117156101da57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929594936020810193503591505064010000000081111561022a57600080fd5b82018360208201111561023c57600080fd5b8035906020019184602083028401116401000000008311171561025e57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610380945050505050565b604080519115158252519081900360200190f35b600154606090819067ffffffffffffffff811180156102ce57600080fd5b506040519080825280602002602001820160405280156102f8578160200160208202803683370190505b50905060005b60015481101561035f576001818154811061031557fe5b9060005260206000200160009054906101000a90046001600160a01b031682828151811061033f57fe5b6001600160a01b03909216602092830291909101909101526001016102fe565b50905090565b60025460045460035467ffffffffffffffff90921691909192565b3360009081526020819052604081205460ff1661039c57600080fd5b868840146103a957600080fd5b82518451146103b757600080fd5b81518451146103c557600080fd5b6006546005548660010167ffffffffffffffff1602014310156103ea5750600061068d565b60025467ffffffffffffffff908116908616101561040a5750600061068d565b60025467ffffffffffffffff868116911614801561043c575067ffffffffffffffff851615158061043c575060035415155b156104495750600061068d565b856104565750600061068d565b60408051601960f81b6020808301919091526000602183018190523060601b60228401526001600160c01b031960c08a901b166036840152603e8084018b905284518085039091018152605e909301909352815191012090805b86518110156106875760006001848984815181106104ca57fe5b60200260200101518985815181106104de57fe5b60200260200101518986815181106104f257fe5b602002602001015160405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015610551573d6000803e3d6000fd5b505060408051601f1901516001600160a01b03811660009081526020819052919091205490925060ff16905061058657600080fd5b826001600160a01b0316816001600160a01b0316116105a457600080fd5b8092508867ffffffffffffffff167fce51ffa16246bcaf0899f6504f473cd0114f430f566cef71ab7e03d3dde42a418b8a85815181106105e057fe5b60200260200101518a86815181106105f457fe5b60200260200101518a878151811061060857fe5b6020026020010151604051808581526020018460ff1660ff16815260200183815260200182815260200194505050505060405180910390a2600754826001011061067e5750505060048790555050436003556002805467ffffffffffffffff191667ffffffffffffffff8616179055600161068d565b506001016104b0565b50600080fd5b97965050505050505056fea26469706673582212202ddf9eda76bf59c0fc65584c0b22d84ecef2c703765de60439596d6ac34c2b7264736f6c634300060b0033" |
||||
|
||||
// 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() view returns(address[])
|
||||
func (_CheckpointOracle *CheckpointOracleCaller) GetAllAdmin(opts *bind.CallOpts) ([]common.Address, error) { |
||||
var out []interface{} |
||||
err := _CheckpointOracle.contract.Call(opts, &out, "GetAllAdmin") |
||||
|
||||
if err != nil { |
||||
return *new([]common.Address), err |
||||
} |
||||
|
||||
out0 := *abi.ConvertType(out[0], new([]common.Address)).(*[]common.Address) |
||||
|
||||
return out0, err |
||||
|
||||
} |
||||
|
||||
// GetAllAdmin is a free data retrieval call binding the contract method 0x45848dfc.
|
||||
//
|
||||
// Solidity: function GetAllAdmin() view 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() view 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() view returns(uint64, bytes32, uint256)
|
||||
func (_CheckpointOracle *CheckpointOracleCaller) GetLatestCheckpoint(opts *bind.CallOpts) (uint64, [32]byte, *big.Int, error) { |
||||
var out []interface{} |
||||
err := _CheckpointOracle.contract.Call(opts, &out, "GetLatestCheckpoint") |
||||
|
||||
if err != nil { |
||||
return *new(uint64), *new([32]byte), *new(*big.Int), err |
||||
} |
||||
|
||||
out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) |
||||
out1 := *abi.ConvertType(out[1], new([32]byte)).(*[32]byte) |
||||
out2 := *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) |
||||
|
||||
return out0, out1, out2, err |
||||
|
||||
} |
||||
|
||||
// GetLatestCheckpoint is a free data retrieval call binding the contract method 0x4d6a304c.
|
||||
//
|
||||
// Solidity: function GetLatestCheckpoint() view 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() view 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 |
||||
} |
@ -1,174 +0,0 @@ |
||||
pragma solidity ^0.6.0; |
||||
|
||||
/** |
||||
* @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; |
||||
} |
@ -1,97 +0,0 @@ |
||||
// 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 checkpointoracle is a an on-chain light client checkpoint oracle.
|
||||
package checkpointoracle |
||||
|
||||
//go:generate solc contract/oracle.sol --combined-json bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc,metadata,hashes --optimize -o ./ --overwrite
|
||||
//go:generate go run ../../cmd/abigen --pkg contract --out contract/oracle.go --combined-json ./combined.json
|
||||
|
||||
import ( |
||||
"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 checkpoint oracle contract.
|
||||
type CheckpointOracle struct { |
||||
address common.Address |
||||
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{address: contractAddr, contract: c}, nil |
||||
} |
||||
|
||||
// ContractAddr returns the address of contract.
|
||||
func (oracle *CheckpointOracle) ContractAddr() common.Address { |
||||
return oracle.address |
||||
} |
||||
|
||||
// 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 && 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(opts *bind.TransactOpts, 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(opts, rnum, rhash, common.BytesToHash(hash), index, v, r, s) |
||||
} |
@ -1,342 +0,0 @@ |
||||
// 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 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
|
||||
contractBackend := backends.NewSimulatedBackend( |
||||
core.GenesisAlloc{ |
||||
accounts[0].addr: {Balance: big.NewInt(10000000000000000)}, |
||||
accounts[1].addr: {Balance: big.NewInt(10000000000000000)}, |
||||
accounts[2].addr: {Balance: big.NewInt(10000000000000000)}, |
||||
}, 10000000, |
||||
) |
||||
defer contractBackend.Close() |
||||
|
||||
transactOpts, _ := bind.NewKeyedTransactorWithChainID(accounts[0].key, big.NewInt(1337)) |
||||
|
||||
// 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, _ := 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") |
||||
} |
@ -1,170 +0,0 @@ |
||||
// Copyright 2020 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 wrapper of checkpoint oracle contract with
|
||||
// additional rules defined. This package can be used both in LES client or
|
||||
// server side for offering oracle related APIs.
|
||||
package checkpointoracle |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"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 can
|
||||
// be verified by clients locally during the checkpoint syncing.
|
||||
type CheckpointOracle struct { |
||||
config *params.CheckpointOracleConfig |
||||
contract *checkpointoracle.CheckpointOracle |
||||
|
||||
running int32 // Flag whether the contract backend is set or not
|
||||
getLocal func(uint64) params.TrustedCheckpoint // Function used to retrieve local checkpoint
|
||||
|
||||
checkMu sync.Mutex // Mutex to sync access to the fields below
|
||||
lastCheckTime time.Time // Time we last checked the checkpoint
|
||||
lastCheckPoint *params.TrustedCheckpoint // The last stable checkpoint
|
||||
lastCheckPointHeight uint64 // The height of last stable checkpoint
|
||||
} |
||||
|
||||
// New creates a checkpoint oracle handler with given configs and callback.
|
||||
func New(config *params.CheckpointOracleConfig, getLocal func(uint64) params.TrustedCheckpoint) *CheckpointOracle { |
||||
return &CheckpointOracle{ |
||||
config: config, |
||||
getLocal: getLocal, |
||||
} |
||||
} |
||||
|
||||
// Start binds the contract backend, initializes the oracle instance
|
||||
// and marks the status as available.
|
||||
func (oracle *CheckpointOracle) Start(backend bind.ContractBackend) { |
||||
contract, err := checkpointoracle.NewCheckpointOracle(oracle.config.Address, backend) |
||||
if err != nil { |
||||
log.Error("Oracle contract binding failed", "err", err) |
||||
return |
||||
} |
||||
if !atomic.CompareAndSwapInt32(&oracle.running, 0, 1) { |
||||
log.Error("Already bound and listening to registrar") |
||||
return |
||||
} |
||||
oracle.contract = contract |
||||
} |
||||
|
||||
// IsRunning returns an indicator whether the oracle is running.
|
||||
func (oracle *CheckpointOracle) IsRunning() bool { |
||||
return atomic.LoadInt32(&oracle.running) == 1 |
||||
} |
||||
|
||||
// Contract returns the underlying raw checkpoint oracle contract.
|
||||
func (oracle *CheckpointOracle) Contract() *checkpointoracle.CheckpointOracle { |
||||
return oracle.contract |
||||
} |
||||
|
||||
// StableCheckpoint returns the stable checkpoint which was generated by local
|
||||
// indexers and announced by trusted signers.
|
||||
func (oracle *CheckpointOracle) StableCheckpoint() (*params.TrustedCheckpoint, uint64) { |
||||
oracle.checkMu.Lock() |
||||
defer oracle.checkMu.Unlock() |
||||
if time.Since(oracle.lastCheckTime) < 1*time.Minute { |
||||
return oracle.lastCheckPoint, oracle.lastCheckPointHeight |
||||
} |
||||
// Look it up properly
|
||||
// Retrieve the latest checkpoint from the contract, abort if empty
|
||||
latest, hash, height, err := oracle.contract.Contract().GetLatestCheckpoint(nil) |
||||
oracle.lastCheckTime = time.Now() |
||||
if err != nil || (latest == 0 && hash == [32]byte{}) { |
||||
oracle.lastCheckPointHeight = 0 |
||||
oracle.lastCheckPoint = nil |
||||
return oracle.lastCheckPoint, oracle.lastCheckPointHeight |
||||
} |
||||
local := oracle.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, no stable checkpoint will be returned.
|
||||
if local.HashEqual(hash) { |
||||
oracle.lastCheckPointHeight = height.Uint64() |
||||
oracle.lastCheckPoint = &local |
||||
return oracle.lastCheckPoint, oracle.lastCheckPointHeight |
||||
} |
||||
return nil, 0 |
||||
} |
||||
|
||||
// VerifySigners recovers the signer addresses according to the signature and
|
||||
// checks whether there are enough approvals to finalize the checkpoint.
|
||||
func (oracle *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(oracle.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(oracle.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 oracle.config.Signers { |
||||
if s == signer { |
||||
signers = append(signers, signer) |
||||
checked[signer] = struct{}{} |
||||
} |
||||
} |
||||
} |
||||
threshold := oracle.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 |
||||
} |
Loading…
Reference in new issue