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>
This commit is contained in:
Péter Szilágyi 2023-04-24 09:37:10 +03:00 committed by GitHub
parent d3ece3a07c
commit 1e556d220c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 65 additions and 3271 deletions

View File

@ -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.*

View File

@ -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 = &params.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 = &params.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))})
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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;
}

View File

@ -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)
}

View File

@ -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")
}

View File

@ -211,10 +211,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// Permit the downloader to use the trie cache allowance during fast sync // Permit the downloader to use the trie cache allowance during fast sync
cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit
checkpoint := config.Checkpoint
if checkpoint == nil {
checkpoint = params.TrustedCheckpoints[eth.blockchain.Genesis().Hash()]
}
if eth.handler, err = newHandler(&handlerConfig{ if eth.handler, err = newHandler(&handlerConfig{
Database: chainDb, Database: chainDb,
Chain: eth.blockchain, Chain: eth.blockchain,
@ -224,7 +220,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
Sync: config.SyncMode, Sync: config.SyncMode,
BloomCache: uint64(cacheLimit), BloomCache: uint64(cacheLimit),
EventMux: eth.eventMux, EventMux: eth.eventMux,
Checkpoint: checkpoint,
RequiredBlocks: config.RequiredBlocks, RequiredBlocks: config.RequiredBlocks,
}); err != nil { }); err != nil {
return nil, err return nil, err

View File

@ -101,7 +101,6 @@ type Downloader struct {
mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode mode atomic.Uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode
mux *event.TypeMux // Event multiplexer to announce sync operation events mux *event.TypeMux // Event multiplexer to announce sync operation events
checkpoint uint64 // Checkpoint block number to enforce head against (e.g. snap sync)
genesis uint64 // Genesis block number to limit sync to (e.g. light client CHT) genesis uint64 // Genesis block number to limit sync to (e.g. light client CHT)
queue *queue // Scheduler for selecting the hashes to download queue *queue // Scheduler for selecting the hashes to download
peers *peerSet // Set of active peers from which download can proceed peers *peerSet // Set of active peers from which download can proceed
@ -219,14 +218,13 @@ type BlockChain interface {
} }
// New creates a new downloader to fetch hashes and blocks from remote peers. // New creates a new downloader to fetch hashes and blocks from remote peers.
func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func()) *Downloader { func New(stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, success func()) *Downloader {
if lightchain == nil { if lightchain == nil {
lightchain = chain lightchain = chain
} }
dl := &Downloader{ dl := &Downloader{
stateDB: stateDb, stateDB: stateDb,
mux: mux, mux: mux,
checkpoint: checkpoint,
queue: newQueue(blockCacheMaxItems, blockCacheInitialItems), queue: newQueue(blockCacheMaxItems, blockCacheInitialItems),
peers: newPeerSet(), peers: newPeerSet(),
blockchain: chain, blockchain: chain,
@ -593,11 +591,9 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd *
d.ancientLimit = 0 d.ancientLimit = 0
} }
} else { } else {
// Legacy sync, use any hardcoded checkpoints or the best announcement // Legacy sync, use the best announcement we have from the remote peer.
// we have from the remote peer. TODO(karalabe): Drop this pathway. // TODO(karalabe): Drop this pathway.
if d.checkpoint != 0 && d.checkpoint > fullMaxForkAncestry+1 { if height > fullMaxForkAncestry+1 {
d.ancientLimit = d.checkpoint
} else if height > fullMaxForkAncestry+1 {
d.ancientLimit = height - fullMaxForkAncestry - 1 d.ancientLimit = height - fullMaxForkAncestry - 1
} else { } else {
d.ancientLimit = 0 d.ancientLimit = 0
@ -742,13 +738,10 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty
if len(headers) == 0 || len(headers) > fetch { if len(headers) == 0 || len(headers) > fetch {
return nil, nil, fmt.Errorf("%w: returned headers %d != requested %d", errBadPeer, len(headers), fetch) return nil, nil, fmt.Errorf("%w: returned headers %d != requested %d", errBadPeer, len(headers), fetch)
} }
// The first header needs to be the head, validate against the checkpoint // The first header needs to be the head, validate against the request. If
// and request. If only 1 header was returned, make sure there's no pivot // only 1 header was returned, make sure there's no pivot or there was not
// or there was not one requested. // one requested.
head = headers[0] head = headers[0]
if (mode == SnapSync || mode == LightSync) && head.Number.Uint64() < d.checkpoint {
return nil, nil, fmt.Errorf("%w: remote head %d below checkpoint %d", errUnsyncedPeer, head.Number, d.checkpoint)
}
if len(headers) == 1 { if len(headers) == 1 {
if mode == SnapSync && head.Number.Uint64() > uint64(fsMinFullBlocks) { if mode == SnapSync && head.Number.Uint64() > uint64(fsMinFullBlocks) {
return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer) return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer)

View File

@ -17,7 +17,6 @@
package downloader package downloader
import ( import (
"errors"
"fmt" "fmt"
"math/big" "math/big"
"os" "os"
@ -82,7 +81,7 @@ func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
chain: chain, chain: chain,
peers: make(map[string]*downloadTesterPeer), peers: make(map[string]*downloadTesterPeer),
} }
tester.downloader = New(0, db, new(event.TypeMux), tester.chain, nil, tester.dropPeer, success) tester.downloader = New(db, new(event.TypeMux), tester.chain, nil, tester.dropPeer, success)
return tester return tester
} }
@ -1409,44 +1408,6 @@ func TestRemoteHeaderRequestSpan(t *testing.T) {
} }
} }
// Tests that peers below a pre-configured checkpoint block are prevented from
// being fast-synced from, avoiding potential cheap eclipse attacks.
func TestCheckpointEnforcement66Full(t *testing.T) { testCheckpointEnforcement(t, eth.ETH66, FullSync) }
func TestCheckpointEnforcement66Snap(t *testing.T) { testCheckpointEnforcement(t, eth.ETH66, SnapSync) }
func TestCheckpointEnforcement66Light(t *testing.T) {
testCheckpointEnforcement(t, eth.ETH66, LightSync)
}
func TestCheckpointEnforcement67Full(t *testing.T) { testCheckpointEnforcement(t, eth.ETH67, FullSync) }
func TestCheckpointEnforcement67Snap(t *testing.T) { testCheckpointEnforcement(t, eth.ETH67, SnapSync) }
func TestCheckpointEnforcement67Light(t *testing.T) {
testCheckpointEnforcement(t, eth.ETH67, LightSync)
}
func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) {
// Create a new tester with a particular hard coded checkpoint block
tester := newTester(t)
defer tester.terminate()
tester.downloader.checkpoint = uint64(fsMinFullBlocks) + 256
chain := testChainBase.shorten(int(tester.downloader.checkpoint) - 1)
// Attempt to sync with the peer and validate the result
tester.newPeer("peer", protocol, chain.blocks[1:])
var expect error
if mode == SnapSync || mode == LightSync {
expect = errUnsyncedPeer
}
if err := tester.sync("peer", nil, mode); !errors.Is(err, expect) {
t.Fatalf("block sync error mismatch: have %v, want %v", err, expect)
}
if mode == SnapSync || mode == LightSync {
assertOwnChain(t, tester, 1)
} else {
assertOwnChain(t, tester, len(chain.blocks))
}
}
// Tests that peers below a pre-configured checkpoint block are prevented from // Tests that peers below a pre-configured checkpoint block are prevented from
// being fast-synced from, avoiding potential cheap eclipse attacks. // being fast-synced from, avoiding potential cheap eclipse attacks.
func TestBeaconSync66Full(t *testing.T) { testBeaconSync(t, eth.ETH66, FullSync) } func TestBeaconSync66Full(t *testing.T) { testBeaconSync(t, eth.ETH66, FullSync) }

View File

@ -147,7 +147,6 @@ type Config struct {
LightPeers int `toml:",omitempty"` // Maximum number of LES client peers LightPeers int `toml:",omitempty"` // Maximum number of LES client peers
LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning
LightNoSyncServe bool `toml:",omitempty"` // Whether to serve light clients before syncing LightNoSyncServe bool `toml:",omitempty"` // Whether to serve light clients before syncing
SyncFromCheckpoint bool `toml:",omitempty"` // Whether to sync the header chain from the configured checkpoint
// Ultra Light client options // Ultra Light client options
UltraLightServers []string `toml:",omitempty"` // List of trusted ultra light servers UltraLightServers []string `toml:",omitempty"` // List of trusted ultra light servers
@ -199,12 +198,6 @@ type Config struct {
// send-transaction variants. The unit is ether. // send-transaction variants. The unit is ether.
RPCTxFeeCap float64 RPCTxFeeCap float64
// Checkpoint is a hardcoded checkpoint which can be nil.
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
// CheckpointOracle is the configuration for checkpoint oracle.
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
// OverrideShanghai (TODO: remove after the fork) // OverrideShanghai (TODO: remove after the fork)
OverrideShanghai *uint64 `toml:",omitempty"` OverrideShanghai *uint64 `toml:",omitempty"`
} }

View File

@ -12,7 +12,6 @@ import (
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/gasprice"
"github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params"
) )
// MarshalTOML marshals as TOML. // MarshalTOML marshals as TOML.
@ -33,7 +32,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
LightPeers int `toml:",omitempty"` LightPeers int `toml:",omitempty"`
LightNoPrune bool `toml:",omitempty"` LightNoPrune bool `toml:",omitempty"`
LightNoSyncServe bool `toml:",omitempty"` LightNoSyncServe bool `toml:",omitempty"`
SyncFromCheckpoint bool `toml:",omitempty"`
UltraLightServers []string `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"`
UltraLightFraction int `toml:",omitempty"` UltraLightFraction int `toml:",omitempty"`
UltraLightOnlyAnnounce bool `toml:",omitempty"` UltraLightOnlyAnnounce bool `toml:",omitempty"`
@ -58,8 +56,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
RPCGasCap uint64 RPCGasCap uint64
RPCEVMTimeout time.Duration RPCEVMTimeout time.Duration
RPCTxFeeCap float64 RPCTxFeeCap float64
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
OverrideShanghai *uint64 `toml:",omitempty"` OverrideShanghai *uint64 `toml:",omitempty"`
} }
var enc Config var enc Config
@ -78,7 +74,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.LightPeers = c.LightPeers enc.LightPeers = c.LightPeers
enc.LightNoPrune = c.LightNoPrune enc.LightNoPrune = c.LightNoPrune
enc.LightNoSyncServe = c.LightNoSyncServe enc.LightNoSyncServe = c.LightNoSyncServe
enc.SyncFromCheckpoint = c.SyncFromCheckpoint
enc.UltraLightServers = c.UltraLightServers enc.UltraLightServers = c.UltraLightServers
enc.UltraLightFraction = c.UltraLightFraction enc.UltraLightFraction = c.UltraLightFraction
enc.UltraLightOnlyAnnounce = c.UltraLightOnlyAnnounce enc.UltraLightOnlyAnnounce = c.UltraLightOnlyAnnounce
@ -103,8 +98,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.RPCGasCap = c.RPCGasCap enc.RPCGasCap = c.RPCGasCap
enc.RPCEVMTimeout = c.RPCEVMTimeout enc.RPCEVMTimeout = c.RPCEVMTimeout
enc.RPCTxFeeCap = c.RPCTxFeeCap enc.RPCTxFeeCap = c.RPCTxFeeCap
enc.Checkpoint = c.Checkpoint
enc.CheckpointOracle = c.CheckpointOracle
enc.OverrideShanghai = c.OverrideShanghai enc.OverrideShanghai = c.OverrideShanghai
return &enc, nil return &enc, nil
} }
@ -127,7 +120,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
LightPeers *int `toml:",omitempty"` LightPeers *int `toml:",omitempty"`
LightNoPrune *bool `toml:",omitempty"` LightNoPrune *bool `toml:",omitempty"`
LightNoSyncServe *bool `toml:",omitempty"` LightNoSyncServe *bool `toml:",omitempty"`
SyncFromCheckpoint *bool `toml:",omitempty"`
UltraLightServers []string `toml:",omitempty"` UltraLightServers []string `toml:",omitempty"`
UltraLightFraction *int `toml:",omitempty"` UltraLightFraction *int `toml:",omitempty"`
UltraLightOnlyAnnounce *bool `toml:",omitempty"` UltraLightOnlyAnnounce *bool `toml:",omitempty"`
@ -152,8 +144,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
RPCGasCap *uint64 RPCGasCap *uint64
RPCEVMTimeout *time.Duration RPCEVMTimeout *time.Duration
RPCTxFeeCap *float64 RPCTxFeeCap *float64
Checkpoint *params.TrustedCheckpoint `toml:",omitempty"`
CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"`
OverrideShanghai *uint64 `toml:",omitempty"` OverrideShanghai *uint64 `toml:",omitempty"`
} }
var dec Config var dec Config
@ -205,9 +195,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.LightNoSyncServe != nil { if dec.LightNoSyncServe != nil {
c.LightNoSyncServe = *dec.LightNoSyncServe c.LightNoSyncServe = *dec.LightNoSyncServe
} }
if dec.SyncFromCheckpoint != nil {
c.SyncFromCheckpoint = *dec.SyncFromCheckpoint
}
if dec.UltraLightServers != nil { if dec.UltraLightServers != nil {
c.UltraLightServers = dec.UltraLightServers c.UltraLightServers = dec.UltraLightServers
} }
@ -280,12 +267,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.RPCTxFeeCap != nil { if dec.RPCTxFeeCap != nil {
c.RPCTxFeeCap = *dec.RPCTxFeeCap c.RPCTxFeeCap = *dec.RPCTxFeeCap
} }
if dec.Checkpoint != nil {
c.Checkpoint = dec.Checkpoint
}
if dec.CheckpointOracle != nil {
c.CheckpointOracle = dec.CheckpointOracle
}
if dec.OverrideShanghai != nil { if dec.OverrideShanghai != nil {
c.OverrideShanghai = dec.OverrideShanghai c.OverrideShanghai = dec.OverrideShanghai
} }

View File

@ -38,7 +38,6 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
) )
const ( const (
@ -85,7 +84,6 @@ type handlerConfig struct {
Sync downloader.SyncMode // Whether to snap or full sync Sync downloader.SyncMode // Whether to snap or full sync
BloomCache uint64 // Megabytes to alloc for snap sync bloom BloomCache uint64 // Megabytes to alloc for snap sync bloom
EventMux *event.TypeMux // Legacy event mux, deprecate for `feed` EventMux *event.TypeMux // Legacy event mux, deprecate for `feed`
Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges
RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges RequiredBlocks map[uint64]common.Hash // Hard coded map of required block hashes for sync challenges
} }
@ -96,9 +94,6 @@ type handler struct {
snapSync uint32 // Flag whether snap sync is enabled (gets disabled if we already have blocks) snapSync uint32 // Flag whether snap sync is enabled (gets disabled if we already have blocks)
acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing) acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing)
checkpointNumber uint64 // Block number for the sync progress validator to cross reference
checkpointHash common.Hash // Block hash for the sync progress validator to cross reference
database ethdb.Database database ethdb.Database
txpool txPool txpool txPool
chain *core.BlockChain chain *core.BlockChain
@ -166,11 +161,6 @@ func newHandler(config *handlerConfig) (*handler, error) {
h.snapSync = uint32(1) h.snapSync = uint32(1)
} }
} }
// If we have trusted checkpoints, enforce them on the chain
if config.Checkpoint != nil {
h.checkpointNumber = (config.Checkpoint.SectionIndex+1)*params.CHTFrequency - 1
h.checkpointHash = config.Checkpoint.SectionHead
}
// If sync succeeds, pass a callback to potentially disable snap sync mode // If sync succeeds, pass a callback to potentially disable snap sync mode
// and enable transaction propagation. // and enable transaction propagation.
success := func() { success := func() {
@ -180,19 +170,12 @@ func newHandler(config *handlerConfig) (*handler, error) {
log.Info("Snap sync complete, auto disabling") log.Info("Snap sync complete, auto disabling")
atomic.StoreUint32(&h.snapSync, 0) atomic.StoreUint32(&h.snapSync, 0)
} }
// If we've successfully finished a sync cycle and passed any required // If we've successfully finished a sync cycle, accept transactions from
// checkpoint, enable accepting transactions from the network // the network
head := h.chain.CurrentBlock()
if head.Number.Uint64() >= h.checkpointNumber {
// Checkpoint passed, sanity check the timestamp to have a fallback mechanism
// for non-checkpointed (number = 0) private networks.
if head.Time >= uint64(time.Now().AddDate(0, -1, 0).Unix()) {
atomic.StoreUint32(&h.acceptTxs, 1) atomic.StoreUint32(&h.acceptTxs, 1)
} }
}
}
// Construct the downloader (long sync) // Construct the downloader (long sync)
h.downloader = downloader.New(h.checkpointNumber, config.Database, h.eventMux, h.chain, nil, h.removePeer, success) h.downloader = downloader.New(config.Database, h.eventMux, h.chain, nil, h.removePeer, success)
if ttd := h.chain.Config().TerminalTotalDifficulty; ttd != nil { if ttd := h.chain.Config().TerminalTotalDifficulty; ttd != nil {
if h.chain.Config().TerminalTotalDifficultyPassed { if h.chain.Config().TerminalTotalDifficultyPassed {
log.Info("Chain post-merge, sync via beacon client") log.Info("Chain post-merge, sync via beacon client")
@ -244,16 +227,6 @@ func newHandler(config *handlerConfig) (*handler, error) {
log.Warn("Unexpected insertion activity", ctx...) log.Warn("Unexpected insertion activity", ctx...)
return 0, errors.New("unexpected behavior after transition") return 0, errors.New("unexpected behavior after transition")
} }
// If sync hasn't reached the checkpoint yet, deny importing weird blocks.
//
// Ideally we would also compare the head block's timestamp and similarly reject
// the propagated block if the head is too old. Unfortunately there is a corner
// case when starting new networks, where the genesis might be ancient (0 unix)
// which would prevent full nodes from accepting it.
if h.chain.CurrentBlock().Number.Uint64() < h.checkpointNumber {
log.Warn("Unsynced yet, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash())
return 0, nil
}
// If snap sync is running, deny importing weird blocks. This is a problematic // If snap sync is running, deny importing weird blocks. This is a problematic
// clause when starting up a new network, because snap-syncing miners might not // clause when starting up a new network, because snap-syncing miners might not
// accept each others' blocks until a restart. Unfortunately we haven't figured // accept each others' blocks until a restart. Unfortunately we haven't figured
@ -387,58 +360,6 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error {
dead := make(chan struct{}) dead := make(chan struct{})
defer close(dead) defer close(dead)
// If we have a trusted CHT, reject all peers below that (avoid fast sync eclipse)
if h.checkpointHash != (common.Hash{}) {
// Request the peer's checkpoint header for chain height/weight validation
resCh := make(chan *eth.Response)
req, err := peer.RequestHeadersByNumber(h.checkpointNumber, 1, 0, false, resCh)
if err != nil {
return err
}
// Start a timer to disconnect if the peer doesn't reply in time
go func() {
// Ensure the request gets cancelled in case of error/drop
defer req.Close()
timeout := time.NewTimer(syncChallengeTimeout)
defer timeout.Stop()
select {
case res := <-resCh:
headers := ([]*types.Header)(*res.Res.(*eth.BlockHeadersPacket))
if len(headers) == 0 {
// If we're doing a snap sync, we must enforce the checkpoint
// block to avoid eclipse attacks. Unsynced nodes are welcome
// to connect after we're done joining the network.
if atomic.LoadUint32(&h.snapSync) == 1 {
peer.Log().Warn("Dropping unsynced node during sync", "addr", peer.RemoteAddr(), "type", peer.Name())
res.Done <- errors.New("unsynced node cannot serve sync")
return
}
res.Done <- nil
return
}
// Validate the header and either drop the peer or continue
if len(headers) > 1 {
res.Done <- errors.New("too many headers in checkpoint response")
return
}
if headers[0].Hash() != h.checkpointHash {
res.Done <- errors.New("checkpoint hash mismatch")
return
}
res.Done <- nil
case <-timeout.C:
peer.Log().Warn("Checkpoint challenge timed out, dropping", "addr", peer.RemoteAddr(), "type", peer.Name())
h.removePeer(peer.ID())
case <-dead:
// Peer handler terminated, abort all goroutines
}
}()
}
// If we have any explicit peer required block hashes, request them // If we have any explicit peer required block hashes, request them
for number, hash := range h.requiredBlocks { for number, hash := range h.requiredBlocks {
resCh := make(chan *eth.Response) resCh := make(chan *eth.Response)

View File

@ -19,8 +19,6 @@ package eth
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"math/rand"
"sync/atomic"
"testing" "testing"
"time" "time"
@ -38,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
) )
// testEthHandler is a mock event handler to listen for inbound network requests // testEthHandler is a mock event handler to listen for inbound network requests
@ -459,148 +456,6 @@ func testTransactionPropagation(t *testing.T, protocol uint) {
} }
} }
// Tests that post eth protocol handshake, clients perform a mutual checkpoint
// challenge to validate each other's chains. Hash mismatches, or missing ones
// during a fast sync should lead to the peer getting dropped.
func TestCheckpointChallenge(t *testing.T) {
tests := []struct {
syncmode downloader.SyncMode
checkpoint bool
timeout bool
empty bool
match bool
drop bool
}{
// If checkpointing is not enabled locally, don't challenge and don't drop
{downloader.FullSync, false, false, false, false, false},
{downloader.SnapSync, false, false, false, false, false},
// If checkpointing is enabled locally and remote response is empty, only drop during fast sync
{downloader.FullSync, true, false, true, false, false},
{downloader.SnapSync, true, false, true, false, true}, // Special case, fast sync, unsynced peer
// If checkpointing is enabled locally and remote response mismatches, always drop
{downloader.FullSync, true, false, false, false, true},
{downloader.SnapSync, true, false, false, false, true},
// If checkpointing is enabled locally and remote response matches, never drop
{downloader.FullSync, true, false, false, true, false},
{downloader.SnapSync, true, false, false, true, false},
// If checkpointing is enabled locally and remote times out, always drop
{downloader.FullSync, true, true, false, true, true},
{downloader.SnapSync, true, true, false, true, true},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("sync %v checkpoint %v timeout %v empty %v match %v", tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match), func(t *testing.T) {
testCheckpointChallenge(t, tt.syncmode, tt.checkpoint, tt.timeout, tt.empty, tt.match, tt.drop)
})
}
}
func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpoint bool, timeout bool, empty bool, match bool, drop bool) {
// Reduce the checkpoint handshake challenge timeout
defer func(old time.Duration) { syncChallengeTimeout = old }(syncChallengeTimeout)
syncChallengeTimeout = 250 * time.Millisecond
// Create a test handler and inject a CHT into it. The injection is a bit
// ugly, but it beats creating everything manually just to avoid reaching
// into the internals a bit.
handler := newTestHandler()
defer handler.close()
if syncmode == downloader.SnapSync {
atomic.StoreUint32(&handler.handler.snapSync, 1)
} else {
atomic.StoreUint32(&handler.handler.snapSync, 0)
}
var response *types.Header
if checkpoint {
number := (uint64(rand.Intn(500))+1)*params.CHTFrequency - 1
response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")}
handler.handler.checkpointNumber = number
handler.handler.checkpointHash = response.Hash()
}
// Create a challenger peer and a challenged one.
p2pLocal, p2pRemote := p2p.MsgPipe()
defer p2pLocal.Close()
defer p2pRemote.Close()
local := eth.NewPeer(eth.ETH66, p2p.NewPeerPipe(enode.ID{1}, "", nil, p2pLocal), p2pLocal, handler.txpool)
remote := eth.NewPeer(eth.ETH66, p2p.NewPeerPipe(enode.ID{2}, "", nil, p2pRemote), p2pRemote, handler.txpool)
defer local.Close()
defer remote.Close()
handlerDone := make(chan struct{})
go func() {
defer close(handlerDone)
handler.handler.runEthPeer(local, func(peer *eth.Peer) error {
return eth.Handle((*ethHandler)(handler.handler), peer)
})
}()
// Run the handshake locally to avoid spinning up a remote handler.
var (
genesis = handler.chain.Genesis()
head = handler.chain.CurrentBlock()
td = handler.chain.GetTd(head.Hash(), head.Number.Uint64())
)
if err := remote.Handshake(1, td, head.Hash(), genesis.Hash(), forkid.NewIDWithChain(handler.chain), forkid.NewFilter(handler.chain)); err != nil {
t.Fatalf("failed to run protocol handshake")
}
// Connect a new peer and check that we receive the checkpoint challenge.
if checkpoint {
msg, err := p2pRemote.ReadMsg()
if err != nil {
t.Fatalf("failed to read checkpoint challenge: %v", err)
}
request := new(eth.GetBlockHeadersPacket66)
if err := msg.Decode(request); err != nil {
t.Fatalf("failed to decode checkpoint challenge: %v", err)
}
query := request.GetBlockHeadersPacket
if query.Origin.Number != response.Number.Uint64() || query.Amount != 1 || query.Skip != 0 || query.Reverse {
t.Fatalf("challenge mismatch: have [%d, %d, %d, %v] want [%d, %d, %d, %v]",
query.Origin.Number, query.Amount, query.Skip, query.Reverse,
response.Number.Uint64(), 1, 0, false)
}
// Create a block to reply to the challenge if no timeout is simulated.
if !timeout {
if empty {
if err := remote.ReplyBlockHeadersRLP(request.RequestId, []rlp.RawValue{}); err != nil {
t.Fatalf("failed to answer challenge: %v", err)
}
} else if match {
responseRlp, _ := rlp.EncodeToBytes(response)
if err := remote.ReplyBlockHeadersRLP(request.RequestId, []rlp.RawValue{responseRlp}); err != nil {
t.Fatalf("failed to answer challenge: %v", err)
}
} else {
responseRlp, _ := rlp.EncodeToBytes(&types.Header{Number: response.Number})
if err := remote.ReplyBlockHeadersRLP(request.RequestId, []rlp.RawValue{responseRlp}); err != nil {
t.Fatalf("failed to answer challenge: %v", err)
}
}
}
}
// Wait until the test timeout passes to ensure proper cleanup
time.Sleep(syncChallengeTimeout + 300*time.Millisecond)
// Verify that the remote peer is maintained or dropped.
if drop {
<-handlerDone
if peers := handler.handler.peers.len(); peers != 0 {
t.Fatalf("peer count mismatch: have %d, want %d", peers, 0)
}
} else {
if peers := handler.handler.peers.len(); peers != 1 {
t.Fatalf("peer count mismatch: have %d, want %d", peers, 1)
}
}
}
// Tests that blocks are broadcast to a sqrt number of peers only. // Tests that blocks are broadcast to a sqrt number of peers only.
func TestBroadcastBlock1Peer(t *testing.T) { testBroadcastBlock(t, 1, 1) } func TestBroadcastBlock1Peer(t *testing.T) { testBroadcastBlock(t, 1, 1) }
func TestBroadcastBlock2Peers(t *testing.T) { testBroadcastBlock(t, 2, 1) } func TestBroadcastBlock2Peers(t *testing.T) { testBroadcastBlock(t, 2, 1) }

View File

@ -260,16 +260,11 @@ func (h *handler) doSync(op *chainSyncOp) error {
log.Info("Snap sync complete, auto disabling") log.Info("Snap sync complete, auto disabling")
atomic.StoreUint32(&h.snapSync, 0) atomic.StoreUint32(&h.snapSync, 0)
} }
// If we've successfully finished a sync cycle and passed any required checkpoint, // If we've successfully finished a sync cycle, enable accepting transactions
// enable accepting transactions from the network. // from the network.
head := h.chain.CurrentBlock()
if head.Number.Uint64() >= h.checkpointNumber {
// Checkpoint passed, sanity check the timestamp to have a fallback mechanism
// for non-checkpointed (number = 0) private networks.
if head.Time >= uint64(time.Now().AddDate(0, -1, 0).Unix()) {
atomic.StoreUint32(&h.acceptTxs, 1) atomic.StoreUint32(&h.acceptTxs, 1)
}
} head := h.chain.CurrentBlock()
if head.Number.Uint64() > 0 { if head.Number.Uint64() > 0 {
// We've completed a sync cycle, notify all peers of new state. This path is // We've completed a sync cycle, notify all peers of new state. This path is
// essential in star-topology networks where a gateway node needs to notify // essential in star-topology networks where a gateway node needs to notify

View File

@ -21,17 +21,12 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
vfs "github.com/ethereum/go-ethereum/les/vflux/server" vfs "github.com/ethereum/go-ethereum/les/vflux/server"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
) )
var ( var errUnknownBenchmarkType = errors.New("unknown benchmark type")
errNoCheckpoint = errors.New("no local checkpoint provided")
errNotActivated = errors.New("checkpoint registrar is not activated")
errUnknownBenchmarkType = errors.New("unknown benchmark type")
)
// LightServerAPI provides an API to access the LES light server. // LightServerAPI provides an API to access the LES light server.
type LightServerAPI struct { type LightServerAPI struct {
@ -352,57 +347,3 @@ func (api *DebugAPI) FreezeClient(node string) error {
return fmt.Errorf("client %064x is not connected", id[:]) return fmt.Errorf("client %064x is not connected", id[:])
} }
} }
// LightAPI provides an API to access the LES light server or light client.
type LightAPI struct {
backend *lesCommons
}
// NewLightAPI creates a new LES service API.
func NewLightAPI(backend *lesCommons) *LightAPI {
return &LightAPI{backend: backend}
}
// LatestCheckpoint returns the latest local checkpoint package.
//
// The checkpoint package consists of 4 strings:
//
// result[0], hex encoded latest section index
// result[1], 32 bytes hex encoded latest section head hash
// result[2], 32 bytes hex encoded latest section canonical hash trie root hash
// result[3], 32 bytes hex encoded latest section bloom trie root hash
func (api *LightAPI) LatestCheckpoint() ([4]string, error) {
var res [4]string
cp := api.backend.latestLocalCheckpoint()
if cp.Empty() {
return res, errNoCheckpoint
}
res[0] = hexutil.EncodeUint64(cp.SectionIndex)
res[1], res[2], res[3] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex()
return res, nil
}
// GetCheckpoint returns the specific local checkpoint package.
//
// The checkpoint package consists of 3 strings:
//
// result[0], 32 bytes hex encoded latest section head hash
// result[1], 32 bytes hex encoded latest section canonical hash trie root hash
// result[2], 32 bytes hex encoded latest section bloom trie root hash
func (api *LightAPI) GetCheckpoint(index uint64) ([3]string, error) {
var res [3]string
cp := api.backend.localCheckpoint(index)
if cp.Empty() {
return res, errNoCheckpoint
}
res[0], res[1], res[2] = cp.SectionHead.Hex(), cp.CHTRoot.Hex(), cp.BloomRoot.Hex()
return res, nil
}
// GetCheckpointContractAddress returns the contract contract address in hex format.
func (api *LightAPI) GetCheckpointContractAddress() (string, error) {
if api.backend.oracle == nil {
return "", errNotActivated
}
return api.backend.oracle.Contract().ContractAddr().Hex(), nil
}

View File

@ -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
}

View File

@ -150,21 +150,14 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune) leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune)
leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer) leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer)
checkpoint := config.Checkpoint
if checkpoint == nil {
checkpoint = params.TrustedCheckpoints[genesisHash]
}
// Note: NewLightChain adds the trusted checkpoint so it needs an ODR with // Note: NewLightChain adds the trusted checkpoint so it needs an ODR with
// indexers already set but not started yet // indexers already set but not started yet
if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine, checkpoint); err != nil { if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil {
return nil, err return nil, err
} }
leth.chainReader = leth.blockchain leth.chainReader = leth.blockchain
leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay) leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
// Set up checkpoint oracle.
leth.oracle = leth.setupOracle(stack, genesisHash, config)
// Note: AddChildIndexer starts the update process for the child // Note: AddChildIndexer starts the update process for the child
leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer) leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer)
leth.chtIndexer.Start(leth.blockchain) leth.chtIndexer.Start(leth.blockchain)
@ -191,7 +184,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
} }
leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, checkpoint, leth) leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, leth)
if leth.handler.ulc != nil { if leth.handler.ulc != nil {
log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.keys), "minTrustedFraction", leth.handler.ulc.fraction) log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.keys), "minTrustedFraction", leth.handler.ulc.fraction)
leth.blockchain.DisableCheckFreq() leth.blockchain.DisableCheckFreq()
@ -309,9 +302,6 @@ func (s *LightEthereum) APIs() []rpc.API {
}, { }, {
Namespace: "net", Namespace: "net",
Service: s.netRPCService, Service: s.netRPCService,
}, {
Namespace: "les",
Service: NewLightAPI(&s.lesCommons),
}, { }, {
Namespace: "vflux", Namespace: "vflux",
Service: s.serverPool.API(), Service: s.serverPool.API(),

View File

@ -33,7 +33,6 @@ import (
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
) )
// clientHandler is responsible for receiving and processing all incoming server // clientHandler is responsible for receiving and processing all incoming server
@ -41,7 +40,6 @@ import (
type clientHandler struct { type clientHandler struct {
ulc *ulc ulc *ulc
forkFilter forkid.Filter forkFilter forkid.Filter
checkpoint *params.TrustedCheckpoint
fetcher *lightFetcher fetcher *lightFetcher
downloader *downloader.Downloader downloader *downloader.Downloader
backend *LightEthereum backend *LightEthereum
@ -54,10 +52,9 @@ type clientHandler struct {
syncEnd func(header *types.Header) // Hook called when the syncing is done syncEnd func(header *types.Header) // Hook called when the syncing is done
} }
func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.TrustedCheckpoint, backend *LightEthereum) *clientHandler { func newClientHandler(ulcServers []string, ulcFraction int, backend *LightEthereum) *clientHandler {
handler := &clientHandler{ handler := &clientHandler{
forkFilter: forkid.NewFilter(backend.blockchain), forkFilter: forkid.NewFilter(backend.blockchain),
checkpoint: checkpoint,
backend: backend, backend: backend,
closeCh: make(chan struct{}), closeCh: make(chan struct{}),
} }
@ -69,12 +66,8 @@ func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.T
handler.ulc = ulc handler.ulc = ulc
log.Info("Enable ultra light client mode") log.Info("Enable ultra light client mode")
} }
var height uint64
if checkpoint != nil {
height = (checkpoint.SectionIndex+1)*params.CHTFrequency - 1
}
handler.fetcher = newLightFetcher(backend.blockchain, backend.engine, backend.peers, handler.ulc, backend.chainDb, backend.reqDist, handler.synchronise) handler.fetcher = newLightFetcher(backend.blockchain, backend.engine, backend.peers, handler.ulc, backend.chainDb, backend.reqDist, handler.synchronise)
handler.downloader = downloader.New(height, backend.chainDb, backend.eventMux, nil, backend.blockchain, handler.removePeer) handler.downloader = downloader.New(0, backend.chainDb, backend.eventMux, nil, backend.blockchain, handler.removePeer)
handler.backend.peers.subscribe((*downloaderPeerNotify)(handler)) handler.backend.peers.subscribe((*downloaderPeerNotify)(handler))
return handler return handler
} }

View File

@ -26,12 +26,8 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/les/checkpointoracle"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -54,7 +50,6 @@ type lesCommons struct {
chainDb, lesDb ethdb.Database chainDb, lesDb ethdb.Database
chainReader chainReader chainReader chainReader
chtIndexer, bloomTrieIndexer *core.ChainIndexer chtIndexer, bloomTrieIndexer *core.ChainIndexer
oracle *checkpointoracle.CheckpointOracle
closeCh chan struct{} closeCh chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
@ -68,7 +63,6 @@ type NodeInfo struct {
Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules
Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block
CHT params.TrustedCheckpoint `json:"cht"` // Trused CHT checkpoint for fast catchup
} }
// makeProtocols creates protocol descriptors for the given LES versions. // makeProtocols creates protocol descriptors for the given LES versions.
@ -101,61 +95,5 @@ func (c *lesCommons) nodeInfo() interface{} {
Genesis: c.genesis, Genesis: c.genesis,
Config: c.chainConfig, Config: c.chainConfig,
Head: hash, Head: hash,
CHT: c.latestLocalCheckpoint(),
} }
} }
// latestLocalCheckpoint finds the common stored section index and returns a set
// of post-processed trie roots (CHT and BloomTrie) associated with the appropriate
// section index and head hash as a local checkpoint package.
func (c *lesCommons) latestLocalCheckpoint() params.TrustedCheckpoint {
sections, _, _ := c.chtIndexer.Sections()
sections2, _, _ := c.bloomTrieIndexer.Sections()
// Cap the section index if the two sections are not consistent.
if sections > sections2 {
sections = sections2
}
if sections == 0 {
// No checkpoint information can be provided.
return params.TrustedCheckpoint{}
}
return c.localCheckpoint(sections - 1)
}
// localCheckpoint returns a set of post-processed trie roots (CHT and BloomTrie)
// associated with the appropriate head hash by specific section index.
//
// The returned checkpoint is only the checkpoint generated by the local indexers,
// not the stable checkpoint registered in the registrar contract.
func (c *lesCommons) localCheckpoint(index uint64) params.TrustedCheckpoint {
sectionHead := c.chtIndexer.SectionHead(index)
return params.TrustedCheckpoint{
SectionIndex: index,
SectionHead: sectionHead,
CHTRoot: light.GetChtRoot(c.chainDb, index, sectionHead),
BloomRoot: light.GetBloomTrieRoot(c.chainDb, index, sectionHead),
}
}
// setupOracle sets up the checkpoint oracle contract client.
func (c *lesCommons) setupOracle(node *node.Node, genesis common.Hash, ethconfig *ethconfig.Config) *checkpointoracle.CheckpointOracle {
config := ethconfig.CheckpointOracle
if config == nil {
// Try loading default config.
config = params.CheckpointOracles[genesis]
}
if config == nil {
log.Info("Checkpoint oracle is not enabled")
return nil
}
if config.Address == (common.Address{}) || uint64(len(config.Signers)) < config.Threshold {
log.Warn("Invalid checkpoint oracle config")
return nil
}
oracle := checkpointoracle.New(config, c.localCheckpoint)
rpcClient, _ := node.Attach()
client := ethclient.NewClient(rpcClient)
oracle.Start(client)
log.Info("Configured checkpoint oracle", "address", config.Address, "signers", len(config.Signers), "threshold", config.Threshold)
return oracle
}

View File

@ -25,8 +25,6 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -149,104 +147,6 @@ func testGappedAnnouncements(t *testing.T, protocol int) {
verifyChainHeight(t, c.handler.fetcher, 5) verifyChainHeight(t, c.handler.fetcher, 5)
} }
func TestTrustedAnnouncementsLes2(t *testing.T) { testTrustedAnnouncement(t, 2) }
func TestTrustedAnnouncementsLes3(t *testing.T) { testTrustedAnnouncement(t, 3) }
func testTrustedAnnouncement(t *testing.T, protocol int) {
//log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
var (
servers []*testServer
teardowns []func()
nodes []*enode.Node
ids []string
cpeers []*clientPeer
config = light.TestServerIndexerConfig
waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
for {
cs, _, _ := cIndexer.Sections()
bts, _, _ := btIndexer.Sections()
if cs >= 2 && bts >= 2 {
break
}
time.Sleep(10 * time.Millisecond)
}
}
)
for i := 0; i < 4; i++ {
s, n, teardown := newTestServerPeer(t, int(2*config.ChtSize+config.ChtConfirms), protocol, waitIndexers)
servers = append(servers, s)
nodes = append(nodes, n)
teardowns = append(teardowns, teardown)
// A half of them are trusted servers.
if i < 2 {
ids = append(ids, n.String())
}
}
netconfig := testnetConfig{
protocol: protocol,
nopruning: true,
ulcServers: ids,
ulcFraction: 60,
}
_, c, teardown := newClientServerEnv(t, netconfig)
defer teardown()
defer func() {
for i := 0; i < len(teardowns); i++ {
teardowns[i]()
}
}()
// Register the assembled checkpoint as hardcoded one.
head := servers[0].chtIndexer.SectionHead(0)
cp := &params.TrustedCheckpoint{
SectionIndex: 0,
SectionHead: head,
CHTRoot: light.GetChtRoot(servers[0].db, 0, head),
BloomRoot: light.GetBloomTrieRoot(servers[0].db, 0, head),
}
c.handler.checkpoint = cp
c.handler.backend.blockchain.AddTrustedCheckpoint(cp)
// Connect all server instances.
for i := 0; i < len(servers); i++ {
_, cp, err := connect(servers[i].handler, nodes[i].ID(), c.handler, protocol, true)
if err != nil {
t.Fatalf("connect server and client failed, err %s", err)
}
cpeers = append(cpeers, cp)
}
newHead := make(chan *types.Header, 1)
c.handler.fetcher.newHeadHook = func(header *types.Header) { newHead <- header }
check := func(height []uint64, expected uint64, callback func()) {
for i := 0; i < len(height); i++ {
for j := 0; j < len(servers); j++ {
h := servers[j].backend.Blockchain().GetHeaderByNumber(height[i])
hash, number := h.Hash(), h.Number.Uint64()
td := rawdb.ReadTd(servers[j].db, hash, number)
// Sign the announcement if necessary.
announce := announceData{hash, number, td, 0, nil}
p := cpeers[j]
if p.announceType == announceTypeSigned {
announce.sign(servers[j].handler.server.privateKey)
}
p.sendAnnounce(announce)
}
}
if callback != nil {
callback()
}
verifyChainHeight(t, c.handler.fetcher, expected)
}
check([]uint64{1}, 1, func() { <-newHead }) // Sequential announcements
check([]uint64{config.ChtSize + config.ChtConfirms}, config.ChtSize+config.ChtConfirms, func() { <-newHead }) // ULC-style light syncing, rollback untrusted headers
check([]uint64{2*config.ChtSize + config.ChtConfirms}, 2*config.ChtSize+config.ChtConfirms, func() { <-newHead }) // Sync the whole chain.
}
func TestInvalidAnnouncesLES2(t *testing.T) { testInvalidAnnounces(t, lpv2) } func TestInvalidAnnouncesLES2(t *testing.T) { testInvalidAnnounces(t, lpv2) }
func TestInvalidAnnouncesLES3(t *testing.T) { testInvalidAnnounces(t, lpv3) } func TestInvalidAnnouncesLES3(t *testing.T) { testInvalidAnnounces(t, lpv3) }
func TestInvalidAnnouncesLES4(t *testing.T) { testInvalidAnnounces(t, lpv4) } func TestInvalidAnnouncesLES4(t *testing.T) { testInvalidAnnounces(t, lpv4) }

View File

@ -39,7 +39,6 @@ import (
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -345,10 +344,6 @@ type serverPeer struct {
stateSince, stateRecent uint64 // The range of state server peer can serve. stateSince, stateRecent uint64 // The range of state server peer can serve.
txHistory uint64 // The length of available tx history, 0 means all, 1 means disabled txHistory uint64 // The length of available tx history, 0 means all, 1 means disabled
// Advertised checkpoint fields
checkpointNumber uint64 // The block height which the checkpoint is registered.
checkpoint params.TrustedCheckpoint // The advertised checkpoint sent by server.
fcServer *flowcontrol.ServerNode // Client side mirror token bucket. fcServer *flowcontrol.ServerNode // Client side mirror token bucket.
vtLock sync.Mutex vtLock sync.Mutex
nodeValueTracker *vfc.NodeValueTracker nodeValueTracker *vfc.NodeValueTracker
@ -661,9 +656,6 @@ func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter
p.fcServer = flowcontrol.NewServerNode(sParams, &mclock.System{}) p.fcServer = flowcontrol.NewServerNode(sParams, &mclock.System{})
p.fcCosts = MRC.decode(ProtocolLengths[uint(p.version)]) p.fcCosts = MRC.decode(ProtocolLengths[uint(p.version)])
recv.get("checkpoint/value", &p.checkpoint)
recv.get("checkpoint/registerHeight", &p.checkpointNumber)
if !p.onlyAnnounce { if !p.onlyAnnounce {
for msgCode := range reqAvgTimeCost { for msgCode := range reqAvgTimeCost {
if p.fcCosts[msgCode] == nil { if p.fcCosts[msgCode] == nil {
@ -1046,16 +1038,6 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge
*lists = (*lists).add("flowControl/MRC", costList) *lists = (*lists).add("flowControl/MRC", costList)
p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)]) p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)])
p.fcParams = server.defParams p.fcParams = server.defParams
// Add advertised checkpoint and register block height which
// client can verify the checkpoint validity.
if server.oracle != nil && server.oracle.IsRunning() {
cp, height := server.oracle.StableCheckpoint()
if cp != nil {
*lists = (*lists).add("checkpoint/value", cp)
*lists = (*lists).add("checkpoint/registerHeight", height)
}
}
}, func(recv keyValueMap) error { }, func(recv keyValueMap) error {
p.server = recv.get("flowControl/MRR", nil) == nil p.server = recv.get("flowControl/MRR", nil) == nil
if p.server { if p.server {

View File

@ -117,7 +117,6 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les
} }
srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), issync) srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), issync)
srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config) srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config)
srv.oracle = srv.setupOracle(node, e.BlockChain().Genesis().Hash(), config)
// Initialize the bloom trie indexer. // Initialize the bloom trie indexer.
e.BloomIndexer().AddChildIndexer(srv.bloomTrieIndexer) e.BloomIndexer().AddChildIndexer(srv.bloomTrieIndexer)
@ -142,12 +141,6 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les
srv.clientPool.Start() srv.clientPool.Start()
srv.clientPool.SetDefaultFactors(defaultPosFactors, defaultNegFactors) srv.clientPool.SetDefaultFactors(defaultPosFactors, defaultNegFactors)
srv.vfluxServer.Register(srv.clientPool, "les", "Ethereum light client service") srv.vfluxServer.Register(srv.clientPool, "les", "Ethereum light client service")
checkpoint := srv.latestLocalCheckpoint()
if !checkpoint.Empty() {
log.Info("Loaded latest checkpoint", "section", checkpoint.SectionIndex, "head", checkpoint.SectionHead,
"chtroot", checkpoint.CHTRoot, "bloomroot", checkpoint.BloomRoot)
}
srv.chtIndexer.Start(e.BlockChain()) srv.chtIndexer.Start(e.BlockChain())
node.RegisterProtocols(srv.Protocols()) node.RegisterProtocols(srv.Protocols())
@ -158,10 +151,6 @@ func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*Les
func (s *LesServer) APIs() []rpc.API { func (s *LesServer) APIs() []rpc.API {
return []rpc.API{ return []rpc.API{
{
Namespace: "les",
Service: NewLightAPI(&s.lesCommons),
},
{ {
Namespace: "les", Namespace: "les",
Service: NewLightServerAPI(s), Service: NewLightServerAPI(s),

View File

@ -17,76 +17,14 @@
package les package les
import ( import (
"context"
"errors"
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/les/downloader" "github.com/ethereum/go-ethereum/les/downloader"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
) )
var errInvalidCheckpoint = errors.New("invalid advertised checkpoint")
const (
// lightSync starts syncing from the current highest block.
// If the chain is empty, syncing the entire header chain.
lightSync = iota
// legacyCheckpointSync starts syncing from a hardcoded checkpoint.
legacyCheckpointSync
// checkpointSync starts syncing from a checkpoint signed by trusted
// signer or hardcoded checkpoint for compatibility.
checkpointSync
)
// validateCheckpoint verifies the advertised checkpoint by peer is valid or not.
//
// Each network has several hard-coded checkpoint signer addresses. Only the
// checkpoint issued by the specified signer is considered valid.
//
// In addition to the checkpoint registered in the registrar contract, there are
// several legacy hardcoded checkpoints in our codebase. These checkpoints are
// also considered as valid.
func (h *clientHandler) validateCheckpoint(peer *serverPeer) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Fetch the block header corresponding to the checkpoint registration.
wrapPeer := &peerConnection{handler: h, peer: peer}
header, err := wrapPeer.RetrieveSingleHeaderByNumber(ctx, peer.checkpointNumber)
if err != nil {
return err
}
// Fetch block logs associated with the block header.
logs, err := light.GetUntrustedBlockLogs(ctx, h.backend.odr, header)
if err != nil {
return err
}
events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, peer.checkpoint.SectionIndex, peer.checkpoint.Hash())
if len(events) == 0 {
return errInvalidCheckpoint
}
var (
index = events[0].Index
hash = events[0].CheckpointHash
signatures [][]byte
)
for _, event := range events {
signatures = append(signatures, append(event.R[:], append(event.S[:], event.V)...))
}
valid, signers := h.backend.oracle.VerifySigners(index, hash, signatures)
if !valid {
return errInvalidCheckpoint
}
log.Warn("Verified advertised checkpoint", "peer", peer.id, "signers", len(signers))
return nil
}
// synchronise tries to sync up our local chain with a remote peer. // synchronise tries to sync up our local chain with a remote peer.
func (h *clientHandler) synchronise(peer *serverPeer) { func (h *clientHandler) synchronise(peer *serverPeer) {
// Short circuit if the peer is nil. // Short circuit if the peer is nil.
@ -99,99 +37,13 @@ func (h *clientHandler) synchronise(peer *serverPeer) {
if currentTd != nil && peer.Td().Cmp(currentTd) < 0 { if currentTd != nil && peer.Td().Cmp(currentTd) < 0 {
return return
} }
// Recap the checkpoint. The light client may be connected to several different // Notify testing framework if syncing has completed (for testing purpose).
// versions of the server.
// (1) Old version server which can not provide stable checkpoint in the
// handshake packet.
// => Use local checkpoint or empty checkpoint
// (2) New version server but simple checkpoint syncing is not enabled
// (e.g. mainnet, new testnet or private network)
// => Use local checkpoint or empty checkpoint
// (3) New version server but the provided stable checkpoint is even lower
// than the local one.
// => Use local checkpoint
// (4) New version server with valid and higher stable checkpoint
// => Use provided checkpoint
var (
local bool
checkpoint = &peer.checkpoint
)
if h.checkpoint != nil && h.checkpoint.SectionIndex >= peer.checkpoint.SectionIndex {
local, checkpoint = true, h.checkpoint
}
// Replace the checkpoint with locally configured one If it's required by
// users. Nil checkpoint means synchronization from the scratch.
if h.backend.config.SyncFromCheckpoint {
local, checkpoint = true, h.backend.config.Checkpoint
if h.backend.config.Checkpoint == nil {
checkpoint = &params.TrustedCheckpoint{}
}
}
// Determine whether we should run checkpoint syncing or normal light syncing.
//
// Here has four situations that we will disable the checkpoint syncing:
//
// 1. The checkpoint is empty
// 2. The latest head block of the local chain is above the checkpoint.
// 3. The checkpoint is local(replaced with local checkpoint)
// 4. For some networks the checkpoint syncing is not activated.
mode := checkpointSync
switch {
case checkpoint.Empty():
mode = lightSync
log.Debug("Disable checkpoint syncing", "reason", "empty checkpoint")
case latest.Number.Uint64() >= (checkpoint.SectionIndex+1)*h.backend.iConfig.ChtSize-1:
mode = lightSync
log.Debug("Disable checkpoint syncing", "reason", "local chain beyond the checkpoint")
case local:
mode = legacyCheckpointSync
log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded")
case h.backend.oracle == nil || !h.backend.oracle.IsRunning():
if h.checkpoint == nil {
mode = lightSync // Downgrade to light sync unfortunately.
} else {
checkpoint = h.checkpoint
mode = legacyCheckpointSync
}
log.Debug("Disable checkpoint syncing", "reason", "checkpoint syncing is not activated")
}
// Notify testing framework if syncing has completed(for testing purpose).
defer func() { defer func() {
if h.syncEnd != nil { if h.syncEnd != nil {
h.syncEnd(h.backend.blockchain.CurrentHeader()) h.syncEnd(h.backend.blockchain.CurrentHeader())
} }
}() }()
start := time.Now() start := time.Now()
if mode == checkpointSync || mode == legacyCheckpointSync {
// Validate the advertised checkpoint
if mode == checkpointSync {
if err := h.validateCheckpoint(peer); err != nil {
log.Debug("Failed to validate checkpoint", "reason", err)
h.removePeer(peer.id)
return
}
h.backend.blockchain.AddTrustedCheckpoint(checkpoint)
}
log.Debug("Checkpoint syncing start", "peer", peer.id, "checkpoint", checkpoint.SectionIndex)
// Fetch the start point block header.
//
// For the ethash consensus engine, the start header is the block header
// of the checkpoint.
//
// For the clique consensus engine, the start header is the block header
// of the latest epoch covered by checkpoint.
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
if !checkpoint.Empty() && !h.backend.blockchain.SyncCheckpoint(ctx, checkpoint) {
log.Debug("Sync checkpoint failed")
h.removePeer(peer.id)
return
}
}
if h.syncStart != nil { if h.syncStart != nil {
h.syncStart(h.backend.blockchain.CurrentHeader()) h.syncStart(h.backend.blockchain.CurrentHeader())
} }

View File

@ -18,30 +18,18 @@ package les
import ( import (
"fmt" "fmt"
"math/big"
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/params"
) )
// Test light syncing which will download all headers from genesis. // Test light syncing which will download all headers from genesis.
func TestLightSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 0) } func TestLightSyncingLes3(t *testing.T) { testSyncing(t, lpv3) }
// Test legacy checkpoint syncing which will download tail headers func testSyncing(t *testing.T, protocol int) {
// based on a hardcoded checkpoint.
func TestLegacyCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 1) }
// Test checkpoint syncing which will download tail headers based
// on a verified checkpoint.
func TestCheckpointSyncingLes3(t *testing.T) { testCheckpointSyncing(t, lpv3, 2) }
func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
config := light.TestServerIndexerConfig config := light.TestServerIndexerConfig
waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
@ -66,46 +54,6 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
expected := config.ChtSize + config.ChtConfirms 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 := &params.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.handler.checkpoint = cp
client.handler.backend.blockchain.AddTrustedCheckpoint(cp)
} else {
// Register the assembled checkpoint into oracle.
header := server.backend.Blockchain().CurrentHeader()
data := append([]byte{0x19, 0x00}, append(oracleAddr.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
auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337))
if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(auth, 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.handler.server.oracle.Contract().Contract().GetLatestCheckpoint(nil)
if err != nil || hash == [32]byte{} {
time.Sleep(10 * time.Millisecond)
continue
}
break
}
expected += 1
}
}
done := make(chan error) done := make(chan error)
client.handler.syncEnd = func(header *types.Header) { client.handler.syncEnd = func(header *types.Header) {
if header.Number.Uint64() == expected { if header.Number.Uint64() == expected {
@ -133,258 +81,3 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
t.Error("checkpoint syncing timeout") t.Error("checkpoint syncing timeout")
} }
} }
func TestMissOracleBackendLES3(t *testing.T) { testMissOracleBackend(t, true, lpv3) }
func TestMissOracleBackendNoCheckpointLES3(t *testing.T) { testMissOracleBackend(t, false, lpv3) }
func testMissOracleBackend(t *testing.T, hasCheckpoint bool, protocol 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 128+1 blocks (totally 1 CHT section)
netconfig := testnetConfig{
blocks: int(config.ChtSize + config.ChtConfirms),
protocol: protocol,
indexFn: waitIndexers,
nopruning: true,
}
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown()
expected := config.ChtSize + config.ChtConfirms
s, _, head := server.chtIndexer.Sections()
cp := &params.TrustedCheckpoint{
SectionIndex: 0,
SectionHead: head,
CHTRoot: light.GetChtRoot(server.db, s-1, head),
BloomRoot: light.GetBloomTrieRoot(server.db, s-1, head),
}
// Register the assembled checkpoint into oracle.
header := server.backend.Blockchain().CurrentHeader()
data := append([]byte{0x19, 0x00}, append(oracleAddr.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
auth, _ := bind.NewKeyedTransactorWithChainID(signerKey, big.NewInt(1337))
if _, err := server.handler.server.oracle.Contract().RegisterCheckpoint(auth, 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.handler.server.oracle.Contract().Contract().GetLatestCheckpoint(nil)
if err != nil || hash == [32]byte{} {
time.Sleep(100 * time.Millisecond)
continue
}
break
}
expected += 1
// Explicitly set the oracle as nil. In normal use case it can happen
// that user wants to unlock something which blocks the oracle backend
// initialisation. But at the same time syncing starts.
//
// See https://github.com/ethereum/go-ethereum/issues/20097 for more detail.
//
// In this case, client should run light sync or legacy checkpoint sync
// if hardcoded checkpoint is configured.
client.handler.backend.oracle = nil
// For some private networks it can happen checkpoint syncing is enabled
// but there is no hardcoded checkpoint configured.
if hasCheckpoint {
client.handler.checkpoint = cp
client.handler.backend.blockchain.AddTrustedCheckpoint(cp)
}
done := make(chan error)
client.handler.syncEnd = func(header *types.Header) {
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.
if _, _, err := newTestPeerPair("peer", 2, server.handler, client.handler, false); err != nil {
t.Fatalf("Failed to connect testing peers %v", err)
}
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")
}
}
func TestSyncFromConfiguredCheckpointLES3(t *testing.T) { testSyncFromConfiguredCheckpoint(t, lpv3) }
func testSyncFromConfiguredCheckpoint(t *testing.T, protocol int) {
config := light.TestServerIndexerConfig
waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
for {
cs, _, _ := cIndexer.Sections()
bts, _, _ := btIndexer.Sections()
if cs >= 2 && bts >= 2 {
break
}
time.Sleep(10 * time.Millisecond)
}
}
// Generate 256+1 blocks (totally 2 CHT sections)
netconfig := testnetConfig{
blocks: int(2*config.ChtSize + config.ChtConfirms),
protocol: protocol,
indexFn: waitIndexers,
nopruning: true,
}
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown()
// Configure the local checkpoint(the first section)
head := server.handler.blockchain.GetHeaderByNumber(config.ChtSize - 1).Hash()
cp := &params.TrustedCheckpoint{
SectionIndex: 0,
SectionHead: head,
CHTRoot: light.GetChtRoot(server.db, 0, head),
BloomRoot: light.GetBloomTrieRoot(server.db, 0, head),
}
client.handler.backend.config.SyncFromCheckpoint = true
client.handler.backend.config.Checkpoint = cp
client.handler.checkpoint = cp
client.handler.backend.blockchain.AddTrustedCheckpoint(cp)
var (
start = make(chan error, 1)
end = make(chan error, 1)
expectStart = config.ChtSize - 1
expectEnd = 2*config.ChtSize + config.ChtConfirms
)
client.handler.syncStart = func(header *types.Header) {
if header.Number.Uint64() == expectStart {
start <- nil
} else {
start <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectStart, header.Number)
}
}
client.handler.syncEnd = func(header *types.Header) {
if header.Number.Uint64() == expectEnd {
end <- nil
} else {
end <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectEnd, header.Number)
}
}
// Create connected peer pair.
if _, _, err := newTestPeerPair("peer", 2, server.handler, client.handler, false); err != nil {
t.Fatalf("Failed to connect testing peers %v", err)
}
select {
case err := <-start:
if err != nil {
t.Error("sync failed", err)
}
return
case <-time.NewTimer(10 * time.Second).C:
t.Error("checkpoint syncing timeout")
}
select {
case err := <-end:
if err != nil {
t.Error("sync failed", err)
}
return
case <-time.NewTimer(10 * time.Second).C:
t.Error("checkpoint syncing timeout")
}
}
func TestSyncAll(t *testing.T) { testSyncAll(t, lpv3) }
func testSyncAll(t *testing.T, protocol int) {
config := light.TestServerIndexerConfig
waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) {
for {
cs, _, _ := cIndexer.Sections()
bts, _, _ := btIndexer.Sections()
if cs >= 2 && bts >= 2 {
break
}
time.Sleep(10 * time.Millisecond)
}
}
// Generate 256+1 blocks (totally 2 CHT sections)
netconfig := testnetConfig{
blocks: int(2*config.ChtSize + config.ChtConfirms),
protocol: protocol,
indexFn: waitIndexers,
nopruning: true,
}
server, client, tearDown := newClientServerEnv(t, netconfig)
defer tearDown()
client.handler.backend.config.SyncFromCheckpoint = true
var (
start = make(chan error, 1)
end = make(chan error, 1)
expectStart = uint64(0)
expectEnd = 2*config.ChtSize + config.ChtConfirms
)
client.handler.syncStart = func(header *types.Header) {
if header.Number.Uint64() == expectStart {
start <- nil
} else {
start <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectStart, header.Number)
}
}
client.handler.syncEnd = func(header *types.Header) {
if header.Number.Uint64() == expectEnd {
end <- nil
} else {
end <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expectEnd, header.Number)
}
}
// Create connected peer pair.
if _, _, err := newTestPeerPair("peer", 2, server.handler, client.handler, false); err != nil {
t.Fatalf("Failed to connect testing peers %v", err)
}
select {
case err := <-start:
if err != nil {
t.Error("sync failed", err)
}
return
case <-time.NewTimer(10 * time.Second).C:
t.Error("checkpoint syncing timeout")
}
select {
case err := <-end:
if err != nil {
t.Error("sync failed", err)
}
return
case <-time.NewTimer(10 * time.Second).C:
t.Error("checkpoint syncing timeout")
}
}

View File

@ -29,13 +29,11 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/contracts/checkpointoracle/contract"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -45,7 +43,6 @@ import (
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/les/checkpointoracle"
"github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/les/flowcontrol"
vfs "github.com/ethereum/go-ethereum/les/vflux/server" vfs "github.com/ethereum/go-ethereum/les/vflux/server"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
@ -72,18 +69,11 @@ var (
testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029") testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029")
// Checkpoint oracle relative fields // Checkpoint oracle relative fields
oracleAddr common.Address
signerKey, _ = crypto.GenerateKey() signerKey, _ = crypto.GenerateKey()
signerAddr = crypto.PubkeyToAddress(signerKey.PublicKey) signerAddr = crypto.PubkeyToAddress(signerKey.PublicKey)
) )
var ( var (
// The block frequency for creating checkpoint(only used in test)
sectionSize = big.NewInt(128)
// The number of confirmations needed to generate a checkpoint(only used in test).
processConfirms = big.NewInt(1)
// The token bucket buffer limit for testing purpose. // The token bucket buffer limit for testing purpose.
testBufLimit = uint64(1000000) testBufLimit = uint64(1000000)
@ -117,11 +107,7 @@ func prepare(n int, backend *backends.SimulatedBackend) {
case 0: case 0:
// Builtin-block // Builtin-block
// number: 1 // number: 1
// txs: 2 // txs: 1
// deploy checkpoint contract
auth, _ := bind.NewKeyedTransactorWithChainID(bankKey, big.NewInt(1337))
oracleAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1))
// bankUser transfers some ether to user1 // bankUser transfers some ether to user1
nonce, _ := backend.PendingNonceAt(ctx, bankAddr) nonce, _ := backend.PendingNonceAt(ctx, bankAddr)
@ -201,28 +187,10 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index
GasLimit: 100000000, GasLimit: 100000000,
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
oracle *checkpointoracle.CheckpointOracle
) )
genesis := gspec.MustCommit(db) genesis := gspec.MustCommit(db)
chain, _ := light.NewLightChain(odr, gspec.Config, engine, nil) chain, _ := light.NewLightChain(odr, gspec.Config, engine)
if indexers != nil {
checkpointConfig := &params.CheckpointOracleConfig{
Address: crypto.CreateAddress(bankAddr, 0),
Signers: []common.Address{signerAddr},
Threshold: 1,
}
getLocal := func(index uint64) params.TrustedCheckpoint {
chtIndexer := indexers[0]
sectionHead := chtIndexer.SectionHead(index)
return params.TrustedCheckpoint{
SectionIndex: index,
SectionHead: sectionHead,
CHTRoot: light.GetChtRoot(db, index, sectionHead),
BloomRoot: light.GetBloomTrieRoot(db, index, sectionHead),
}
}
oracle = checkpointoracle.New(checkpointConfig, getLocal)
}
client := &LightEthereum{ client := &LightEthereum{
lesCommons: lesCommons{ lesCommons: lesCommons{
genesis: genesis.Hash(), genesis: genesis.Hash(),
@ -230,7 +198,6 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index
chainConfig: params.AllEthashProtocolChanges, chainConfig: params.AllEthashProtocolChanges,
iConfig: light.TestClientIndexerConfig, iConfig: light.TestClientIndexerConfig,
chainDb: db, chainDb: db,
oracle: oracle,
chainReader: chain, chainReader: chain,
closeCh: make(chan struct{}), closeCh: make(chan struct{}),
}, },
@ -243,11 +210,8 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index
eventMux: evmux, eventMux: evmux,
merger: consensus.NewMerger(rawdb.NewMemoryDatabase()), merger: consensus.NewMerger(rawdb.NewMemoryDatabase()),
} }
client.handler = newClientHandler(ulcServers, ulcFraction, nil, client) client.handler = newClientHandler(ulcServers, ulcFraction, client)
if client.oracle != nil {
client.oracle.Start(backend)
}
client.handler.start() client.handler.start()
return client.handler, func() { return client.handler, func() {
client.handler.stop() client.handler.stop()
@ -262,7 +226,6 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
GasLimit: 100000000, GasLimit: 100000000,
BaseFee: big.NewInt(params.InitialBaseFee), BaseFee: big.NewInt(params.InitialBaseFee),
} }
oracle *checkpointoracle.CheckpointOracle
) )
genesis := gspec.MustCommit(db) genesis := gspec.MustCommit(db)
@ -273,24 +236,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
txpoolConfig := txpool.DefaultConfig txpoolConfig := txpool.DefaultConfig
txpoolConfig.Journal = "" txpoolConfig.Journal = ""
txpool := txpool.NewTxPool(txpoolConfig, gspec.Config, simulation.Blockchain()) txpool := txpool.NewTxPool(txpoolConfig, gspec.Config, simulation.Blockchain())
if indexers != nil {
checkpointConfig := &params.CheckpointOracleConfig{
Address: crypto.CreateAddress(bankAddr, 0),
Signers: []common.Address{signerAddr},
Threshold: 1,
}
getLocal := func(index uint64) params.TrustedCheckpoint {
chtIndexer := indexers[0]
sectionHead := chtIndexer.SectionHead(index)
return params.TrustedCheckpoint{
SectionIndex: index,
SectionHead: sectionHead,
CHTRoot: light.GetChtRoot(db, index, sectionHead),
BloomRoot: light.GetBloomTrieRoot(db, index, sectionHead),
}
}
oracle = checkpointoracle.New(checkpointConfig, getLocal)
}
server := &LesServer{ server := &LesServer{
lesCommons: lesCommons{ lesCommons: lesCommons{
genesis: genesis.Hash(), genesis: genesis.Hash(),
@ -299,7 +245,6 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
iConfig: light.TestServerIndexerConfig, iConfig: light.TestServerIndexerConfig,
chainDb: db, chainDb: db,
chainReader: simulation.Blockchain(), chainReader: simulation.Blockchain(),
oracle: oracle,
closeCh: make(chan struct{}), closeCh: make(chan struct{}),
}, },
peers: newClientPeerSet(), peers: newClientPeerSet(),
@ -316,9 +261,6 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
server.clientPool.Start() server.clientPool.Start()
server.clientPool.SetLimits(10000, 10000) // Assign enough capacity for clientpool server.clientPool.SetLimits(10000, 10000) // Assign enough capacity for clientpool
server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true }) server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true })
if server.oracle != nil {
server.oracle.Start(simulation)
}
server.servingQueue.setThreads(4) server.servingQueue.setThreads(4)
server.handler.start() server.handler.start()
closer := func() { server.Stop() } closer := func() { server.Stop() }

View File

@ -78,7 +78,7 @@ type LightChain struct {
// NewLightChain returns a fully initialised light chain using information // NewLightChain returns a fully initialised light chain using information
// available in the database. It initialises the default Ethereum header // available in the database. It initialises the default Ethereum header
// validator. // validator.
func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine, checkpoint *params.TrustedCheckpoint) (*LightChain, error) { func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine) (*LightChain, error) {
bc := &LightChain{ bc := &LightChain{
chainDb: odr.Database(), chainDb: odr.Database(),
indexerConfig: odr.IndexerConfig(), indexerConfig: odr.IndexerConfig(),
@ -99,9 +99,6 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
if bc.genesisBlock == nil { if bc.genesisBlock == nil {
return nil, core.ErrNoGenesis return nil, core.ErrNoGenesis
} }
if checkpoint != nil {
bc.AddTrustedCheckpoint(checkpoint)
}
if err := bc.loadLastState(); err != nil { if err := bc.loadLastState(); err != nil {
return nil, err return nil, err
} }
@ -116,22 +113,6 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
return bc, nil return bc, nil
} }
// AddTrustedCheckpoint adds a trusted checkpoint to the blockchain
func (lc *LightChain) AddTrustedCheckpoint(cp *params.TrustedCheckpoint) {
if lc.odr.ChtIndexer() != nil {
StoreChtRoot(lc.chainDb, cp.SectionIndex, cp.SectionHead, cp.CHTRoot)
lc.odr.ChtIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead)
}
if lc.odr.BloomTrieIndexer() != nil {
StoreBloomTrieRoot(lc.chainDb, cp.SectionIndex, cp.SectionHead, cp.BloomRoot)
lc.odr.BloomTrieIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead)
}
if lc.odr.BloomIndexer() != nil {
lc.odr.BloomIndexer().AddCheckpoint(cp.SectionIndex, cp.SectionHead)
}
log.Info("Added trusted checkpoint", "block", (cp.SectionIndex+1)*lc.indexerConfig.ChtSize-1, "hash", cp.SectionHead)
}
func (lc *LightChain) getProcInterrupt() bool { func (lc *LightChain) getProcInterrupt() bool {
return atomic.LoadInt32(&lc.procInterrupt) == 1 return atomic.LoadInt32(&lc.procInterrupt) == 1
} }
@ -520,38 +501,6 @@ func (lc *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64) (
// Config retrieves the header chain's chain configuration. // Config retrieves the header chain's chain configuration.
func (lc *LightChain) Config() *params.ChainConfig { return lc.hc.Config() } func (lc *LightChain) Config() *params.ChainConfig { return lc.hc.Config() }
// SyncCheckpoint fetches the checkpoint point block header according to
// the checkpoint provided by the remote peer.
//
// Note if we are running the clique, fetches the last epoch snapshot header
// which covered by checkpoint.
func (lc *LightChain) SyncCheckpoint(ctx context.Context, checkpoint *params.TrustedCheckpoint) bool {
// Ensure the remote checkpoint head is ahead of us
head := lc.CurrentHeader().Number.Uint64()
latest := (checkpoint.SectionIndex+1)*lc.indexerConfig.ChtSize - 1
if clique := lc.hc.Config().Clique; clique != nil {
latest -= latest % clique.Epoch // epoch snapshot for clique
}
if head >= latest {
return true
}
// Retrieve the latest useful header and update to it
if header, err := GetHeaderByNumber(ctx, lc.odr, latest); header != nil && err == nil {
lc.chainmu.Lock()
defer lc.chainmu.Unlock()
// Ensure the chain didn't move past the latest block while retrieving it
if lc.hc.CurrentHeader().Number.Uint64() < header.Number.Uint64() {
log.Info("Updated latest header based on CHT", "number", header.Number, "hash", header.Hash(), "age", common.PrettyAge(time.Unix(int64(header.Time), 0)))
rawdb.WriteHeadHeaderHash(lc.chainDb, header.Hash())
lc.hc.SetCurrentHeader(header)
}
return true
}
return false
}
// LockChain locks the chain mutex for reading so that multiple canonical hashes can be // LockChain locks the chain mutex for reading so that multiple canonical hashes can be
// retrieved while it is guaranteed that they belong to the same version of the chain // retrieved while it is guaranteed that they belong to the same version of the chain
func (lc *LightChain) LockChain() { func (lc *LightChain) LockChain() {

View File

@ -56,7 +56,7 @@ func newCanonical(n int) (ethdb.Database, *LightChain, error) {
db := rawdb.NewMemoryDatabase() db := rawdb.NewMemoryDatabase()
gspec := core.Genesis{Config: params.TestChainConfig} gspec := core.Genesis{Config: params.TestChainConfig}
genesis := gspec.MustCommit(db) genesis := gspec.MustCommit(db)
blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker(), nil) blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker())
// Create and inject the requested chain // Create and inject the requested chain
if n == 0 { if n == 0 {
@ -76,7 +76,7 @@ func newTestLightChain() *LightChain {
Config: params.TestChainConfig, Config: params.TestChainConfig,
} }
gspec.MustCommit(db) gspec.MustCommit(db)
lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker(), nil) lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker())
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -347,7 +347,7 @@ func TestReorgBadHeaderHashes(t *testing.T) {
defer func() { delete(core.BadHashes, headers[3].Hash()) }() defer func() { delete(core.BadHashes, headers[3].Hash()) }()
// Create a new LightChain and check that it rolled back the state. // Create a new LightChain and check that it rolled back the state.
ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker(), nil) ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker())
if err != nil { if err != nil {
t.Fatalf("failed to create new chain manager: %v", err) t.Fatalf("failed to create new chain manager: %v", err)
} }

View File

@ -125,7 +125,6 @@ func (req *BlockRequest) StoreResult(db ethdb.Database) {
// ReceiptsRequest is the ODR request type for retrieving receipts. // ReceiptsRequest is the ODR request type for retrieving receipts.
type ReceiptsRequest struct { type ReceiptsRequest struct {
Untrusted bool // Indicator whether the result retrieved is trusted or not
Hash common.Hash Hash common.Hash
Number uint64 Number uint64
Header *types.Header Header *types.Header
@ -134,9 +133,7 @@ type ReceiptsRequest struct {
// StoreResult stores the retrieved data in local database // StoreResult stores the retrieved data in local database
func (req *ReceiptsRequest) StoreResult(db ethdb.Database) { func (req *ReceiptsRequest) StoreResult(db ethdb.Database) {
if !req.Untrusted {
rawdb.WriteReceipts(db, req.Hash, req.Number, req.Receipts) rawdb.WriteReceipts(db, req.Hash, req.Number, req.Receipts)
}
} }
// ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie // ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie

View File

@ -284,7 +284,7 @@ func testChainOdr(t *testing.T, protocol int, fn odrTestFn) {
gspec.MustCommit(ldb) gspec.MustCommit(ldb)
odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig} odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig}
lightchain, err := NewLightChain(odr, gspec.Config, ethash.NewFullFaker(), nil) lightchain, err := NewLightChain(odr, gspec.Config, ethash.NewFullFaker())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -197,30 +197,6 @@ func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number
return logs, nil return logs, nil
} }
// GetUntrustedBlockLogs retrieves the logs generated by the transactions included in a
// block. The retrieved logs are regarded as untrusted and will not be stored in the
// database. This function should only be used in light client checkpoint syncing.
func GetUntrustedBlockLogs(ctx context.Context, odr OdrBackend, header *types.Header) ([][]*types.Log, error) {
// Retrieve the potentially incomplete receipts from disk or network
hash, number := header.Hash(), header.Number.Uint64()
receipts := rawdb.ReadRawReceipts(odr.Database(), hash, number)
if receipts == nil {
r := &ReceiptsRequest{Hash: hash, Number: number, Header: header, Untrusted: true}
if err := odr.Retrieve(ctx, r); err != nil {
return nil, err
}
receipts = r.Receipts
// Untrusted receipts won't be stored in the database. Therefore
// derived fields computation is unnecessary.
}
// Return the logs without deriving any computed fields on the receipts
logs := make([][]*types.Log, len(receipts))
for i, receipt := range receipts {
logs[i] = receipt.Logs
}
return logs, nil
}
// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to // GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to
// the given bit index and section indexes. // the given bit index and section indexes.
func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint64) ([][]byte, error) { func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint64) ([][]byte, error) {

View File

@ -103,7 +103,7 @@ func TestTxPool(t *testing.T) {
discard: make(chan int, 1), discard: make(chan int, 1),
mined: make(chan int, 1), mined: make(chan int, 1),
} }
lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker(), nil) lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker())
txPermanent = 50 txPermanent = 50
pool := NewTxPool(params.TestChainConfig, lightchain, relay) pool := NewTxPool(params.TestChainConfig, lightchain, relay)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)

View File

@ -17,12 +17,10 @@
package params package params
import ( import (
"encoding/binary"
"fmt" "fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"golang.org/x/crypto/sha3"
) )
// Genesis hashes to enforce below configs on. // Genesis hashes to enforce below configs on.
@ -33,23 +31,6 @@ var (
GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a")
) )
// TrustedCheckpoints associates each known checkpoint with the genesis hash of
// the chain it belongs to.
var TrustedCheckpoints = map[common.Hash]*TrustedCheckpoint{
MainnetGenesisHash: MainnetTrustedCheckpoint,
SepoliaGenesisHash: SepoliaTrustedCheckpoint,
RinkebyGenesisHash: RinkebyTrustedCheckpoint,
GoerliGenesisHash: GoerliTrustedCheckpoint,
}
// CheckpointOracles associates each known checkpoint oracles with the genesis hash of
// the chain it belongs to.
var CheckpointOracles = map[common.Hash]*CheckpointOracleConfig{
MainnetGenesisHash: MainnetCheckpointOracle,
RinkebyGenesisHash: RinkebyCheckpointOracle,
GoerliGenesisHash: GoerliCheckpointOracle,
}
func newUint64(val uint64) *uint64 { return &val } func newUint64(val uint64) *uint64 { return &val }
var ( var (
@ -78,28 +59,6 @@ var (
ShanghaiTime: newUint64(1681338455), ShanghaiTime: newUint64(1681338455),
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
} }
// MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network.
MainnetTrustedCheckpoint = &TrustedCheckpoint{
SectionIndex: 506,
SectionHead: common.HexToHash("0x3d1a139a6fc7764211236ef7c64d9e8c1fe55b358d7414e25277bac1144486cd"),
CHTRoot: common.HexToHash("0xef7fc3321a239a54238593bdf68d82933d903cb533b0d03228a8d958cd35ea77"),
BloomRoot: common.HexToHash("0x51d7bfe7c6397b1caa8b1cb046de4aeaf7e7fbd3fb6c726b60bf750de78809e8"),
}
// MainnetCheckpointOracle contains a set of configs for the main network oracle.
MainnetCheckpointOracle = &CheckpointOracleConfig{
Address: common.HexToAddress("0x9a9070028361F7AAbeB3f2F2Dc07F82C4a98A02a"),
Signers: []common.Address{
common.HexToAddress("0x1b2C260efc720BE89101890E4Db589b44E950527"), // Peter
common.HexToAddress("0x78d1aD571A1A09D60D9BBf25894b44e4C8859595"), // Martin
common.HexToAddress("0x286834935f4A8Cfb4FF4C77D5770C2775aE2b0E7"), // Zsolt
common.HexToAddress("0xb86e2B0Ab5A4B1373e40c51A7C712c70Ba2f9f8E"), // Gary
common.HexToAddress("0x0DF8fa387C602AE62559cC4aFa4972A7045d6707"), // Guillaume
},
Threshold: 2,
}
// SepoliaChainConfig contains the chain parameters to run a node on the Sepolia test network. // SepoliaChainConfig contains the chain parameters to run a node on the Sepolia test network.
SepoliaChainConfig = &ChainConfig{ SepoliaChainConfig = &ChainConfig{
ChainID: big.NewInt(11155111), ChainID: big.NewInt(11155111),
@ -122,15 +81,6 @@ var (
ShanghaiTime: newUint64(1677557088), ShanghaiTime: newUint64(1677557088),
Ethash: new(EthashConfig), Ethash: new(EthashConfig),
} }
// SepoliaTrustedCheckpoint contains the light client trusted checkpoint for the Sepolia test network.
SepoliaTrustedCheckpoint = &TrustedCheckpoint{
SectionIndex: 55,
SectionHead: common.HexToHash("0xb70ea113ab4db9d6e015c5b55d486713f60c40bda666121914a71ce3aec53a75"),
CHTRoot: common.HexToHash("0x206456d8847b66aaf427ed551f55e24cff90241bdb0a02583c761bf8164f78e4"),
BloomRoot: common.HexToHash("0x4369228d59a8fe285fee874c636531091e659b3b1294bb978eb159860a1cede2"),
}
// RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network. // RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network.
RinkebyChainConfig = &ChainConfig{ RinkebyChainConfig = &ChainConfig{
ChainID: big.NewInt(4), ChainID: big.NewInt(4),
@ -153,27 +103,6 @@ var (
Epoch: 30000, Epoch: 30000,
}, },
} }
// RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network.
RinkebyTrustedCheckpoint = &TrustedCheckpoint{
SectionIndex: 373,
SectionHead: common.HexToHash("0x09f6d8f0d08d61025ccf4578dc214220b78013841470d445ed86faab4a5a885a"),
CHTRoot: common.HexToHash("0xef72902b944a111e9fdfee5fb69a5e46f68bf11a1f0bd430321f92d6b66987df"),
BloomRoot: common.HexToHash("0xd0120268729c51dd6fa2714f7f88527adfecbdb08592c671233ad2e0ad7cd835"),
}
// RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle.
RinkebyCheckpointOracle = &CheckpointOracleConfig{
Address: common.HexToAddress("0xebe8eFA441B9302A0d7eaECc277c09d20D684540"),
Signers: []common.Address{
common.HexToAddress("0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3"), // Peter
common.HexToAddress("0x78d1aD571A1A09D60D9BBf25894b44e4C8859595"), // Martin
common.HexToAddress("0x286834935f4A8Cfb4FF4C77D5770C2775aE2b0E7"), // Zsolt
common.HexToAddress("0xb86e2B0Ab5A4B1373e40c51A7C712c70Ba2f9f8E"), // Gary
},
Threshold: 2,
}
// GoerliChainConfig contains the chain parameters to run a node on the Görli test network. // GoerliChainConfig contains the chain parameters to run a node on the Görli test network.
GoerliChainConfig = &ChainConfig{ GoerliChainConfig = &ChainConfig{
ChainID: big.NewInt(5), ChainID: big.NewInt(5),
@ -199,28 +128,6 @@ var (
Epoch: 30000, Epoch: 30000,
}, },
} }
// GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network.
GoerliTrustedCheckpoint = &TrustedCheckpoint{
SectionIndex: 229,
SectionHead: common.HexToHash("0xc5a7b57cb4af7b3d4cc251ac5f29acaac94e7464365358e7ad26129083b7729a"),
CHTRoot: common.HexToHash("0x54c0d5c756d9c48eda26ea13c2a49c2e31f1cb7dfb01514ddc49f3d24272c77e"),
BloomRoot: common.HexToHash("0xd681970a496f6187d089f8c8665a3587b5a78212d79b6ceef97c0dabd0188e56"),
}
// GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle.
GoerliCheckpointOracle = &CheckpointOracleConfig{
Address: common.HexToAddress("0x18CA0E045F0D772a851BC7e48357Bcaab0a0795D"),
Signers: []common.Address{
common.HexToAddress("0x4769bcaD07e3b938B7f43EB7D278Bc7Cb9efFb38"), // Peter
common.HexToAddress("0x78d1aD571A1A09D60D9BBf25894b44e4C8859595"), // Martin
common.HexToAddress("0x286834935f4A8Cfb4FF4C77D5770C2775aE2b0E7"), // Zsolt
common.HexToAddress("0xb86e2B0Ab5A4B1373e40c51A7C712c70Ba2f9f8E"), // Gary
common.HexToAddress("0x0DF8fa387C602AE62559cC4aFa4972A7045d6707"), // Guillaume
},
Threshold: 2,
}
// AllEthashProtocolChanges contains every protocol change (EIPs) introduced // AllEthashProtocolChanges contains every protocol change (EIPs) introduced
// and accepted by the Ethereum core developers into the Ethash consensus. // and accepted by the Ethereum core developers into the Ethash consensus.
AllEthashProtocolChanges = &ChainConfig{ AllEthashProtocolChanges = &ChainConfig{
@ -347,54 +254,6 @@ var NetworkNames = map[string]string{
SepoliaChainConfig.ChainID.String(): "sepolia", SepoliaChainConfig.ChainID.String(): "sepolia",
} }
// TrustedCheckpoint represents a set of post-processed trie roots (CHT and
// BloomTrie) associated with the appropriate section index and head hash. It is
// used to start light syncing from this checkpoint and avoid downloading the
// entire header chain while still being able to securely access old headers/logs.
type TrustedCheckpoint struct {
SectionIndex uint64 `json:"sectionIndex"`
SectionHead common.Hash `json:"sectionHead"`
CHTRoot common.Hash `json:"chtRoot"`
BloomRoot common.Hash `json:"bloomRoot"`
}
// HashEqual returns an indicator comparing the itself hash with given one.
func (c *TrustedCheckpoint) HashEqual(hash common.Hash) bool {
if c.Empty() {
return hash == common.Hash{}
}
return c.Hash() == hash
}
// Hash returns the hash of checkpoint's four key fields(index, sectionHead, chtRoot and bloomTrieRoot).
func (c *TrustedCheckpoint) Hash() common.Hash {
var sectionIndex [8]byte
binary.BigEndian.PutUint64(sectionIndex[:], c.SectionIndex)
w := sha3.NewLegacyKeccak256()
w.Write(sectionIndex[:])
w.Write(c.SectionHead[:])
w.Write(c.CHTRoot[:])
w.Write(c.BloomRoot[:])
var h common.Hash
w.Sum(h[:0])
return h
}
// Empty returns an indicator whether the checkpoint is regarded as empty.
func (c *TrustedCheckpoint) Empty() bool {
return c.SectionHead == (common.Hash{}) || c.CHTRoot == (common.Hash{}) || c.BloomRoot == (common.Hash{})
}
// CheckpointOracleConfig represents a set of checkpoint contract(which acts as an oracle)
// config which used for light client checkpoint syncing.
type CheckpointOracleConfig struct {
Address common.Address `json:"address"`
Signers []common.Address `json:"signers"`
Threshold uint64 `json:"threshold"`
}
// ChainConfig is the core config which determines the blockchain settings. // ChainConfig is the core config which determines the blockchain settings.
// //
// ChainConfig is stored in the database on a per block basis. This means // ChainConfig is stored in the database on a per block basis. This means