forked from cerc-io/plugeth
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:
co-authored by
Gary Rong
parent
d3ece3a07c
commit
1e556d220c
+1
-60
@@ -21,17 +21,12 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
vfs "github.com/ethereum/go-ethereum/les/vflux/server"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoCheckpoint = errors.New("no local checkpoint provided")
|
||||
errNotActivated = errors.New("checkpoint registrar is not activated")
|
||||
errUnknownBenchmarkType = errors.New("unknown benchmark type")
|
||||
)
|
||||
var errUnknownBenchmarkType = errors.New("unknown benchmark type")
|
||||
|
||||
// LightServerAPI provides an API to access the LES light server.
|
||||
type LightServerAPI struct {
|
||||
@@ -352,57 +347,3 @@ func (api *DebugAPI) FreezeClient(node string) error {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+2
-12
@@ -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.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
|
||||
// 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
|
||||
}
|
||||
leth.chainReader = leth.blockchain
|
||||
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
|
||||
leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer)
|
||||
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.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, checkpoint, leth)
|
||||
leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, leth)
|
||||
if leth.handler.ulc != nil {
|
||||
log.Warn("Ultra light client is enabled", "trustedNodes", len(leth.handler.ulc.keys), "minTrustedFraction", leth.handler.ulc.fraction)
|
||||
leth.blockchain.DisableCheckFreq()
|
||||
@@ -309,9 +302,6 @@ func (s *LightEthereum) APIs() []rpc.API {
|
||||
}, {
|
||||
Namespace: "net",
|
||||
Service: s.netRPCService,
|
||||
}, {
|
||||
Namespace: "les",
|
||||
Service: NewLightAPI(&s.lesCommons),
|
||||
}, {
|
||||
Namespace: "vflux",
|
||||
Service: s.serverPool.API(),
|
||||
|
||||
@@ -33,7 +33,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// clientHandler is responsible for receiving and processing all incoming server
|
||||
@@ -41,7 +40,6 @@ import (
|
||||
type clientHandler struct {
|
||||
ulc *ulc
|
||||
forkFilter forkid.Filter
|
||||
checkpoint *params.TrustedCheckpoint
|
||||
fetcher *lightFetcher
|
||||
downloader *downloader.Downloader
|
||||
backend *LightEthereum
|
||||
@@ -54,10 +52,9 @@ type clientHandler struct {
|
||||
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{
|
||||
forkFilter: forkid.NewFilter(backend.blockchain),
|
||||
checkpoint: checkpoint,
|
||||
backend: backend,
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
@@ -69,12 +66,8 @@ func newClientHandler(ulcServers []string, ulcFraction int, checkpoint *params.T
|
||||
handler.ulc = ulc
|
||||
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.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))
|
||||
return handler
|
||||
}
|
||||
|
||||
+5
-67
@@ -26,12 +26,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/les/checkpointoracle"
|
||||
"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/enode"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
@@ -54,7 +50,6 @@ type lesCommons struct {
|
||||
chainDb, lesDb ethdb.Database
|
||||
chainReader chainReader
|
||||
chtIndexer, bloomTrieIndexer *core.ChainIndexer
|
||||
oracle *checkpointoracle.CheckpointOracle
|
||||
|
||||
closeCh chan struct{}
|
||||
wg sync.WaitGroup
|
||||
@@ -63,12 +58,11 @@ type lesCommons struct {
|
||||
// NodeInfo represents a short summary of the Ethereum sub-protocol metadata
|
||||
// known about the host peer.
|
||||
type NodeInfo struct {
|
||||
Network uint64 `json:"network"` // Ethereum network ID (1=Mainnet, Rinkeby=4, Goerli=5)
|
||||
Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain
|
||||
Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
|
||||
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
|
||||
CHT params.TrustedCheckpoint `json:"cht"` // Trused CHT checkpoint for fast catchup
|
||||
Network uint64 `json:"network"` // Ethereum network ID (1=Mainnet, Rinkeby=4, Goerli=5)
|
||||
Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain
|
||||
Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
|
||||
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
|
||||
}
|
||||
|
||||
// makeProtocols creates protocol descriptors for the given LES versions.
|
||||
@@ -101,61 +95,5 @@ func (c *lesCommons) nodeInfo() interface{} {
|
||||
Genesis: c.genesis,
|
||||
Config: c.chainConfig,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -25,8 +25,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -149,104 +147,6 @@ func testGappedAnnouncements(t *testing.T, protocol int) {
|
||||
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 := ¶ms.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 TestInvalidAnnouncesLES3(t *testing.T) { testInvalidAnnounces(t, lpv3) }
|
||||
func TestInvalidAnnouncesLES4(t *testing.T) { testInvalidAnnounces(t, lpv4) }
|
||||
|
||||
-18
@@ -39,7 +39,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
@@ -345,10 +344,6 @@ type serverPeer struct {
|
||||
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
|
||||
|
||||
// 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.
|
||||
vtLock sync.Mutex
|
||||
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.fcCosts = MRC.decode(ProtocolLengths[uint(p.version)])
|
||||
|
||||
recv.get("checkpoint/value", &p.checkpoint)
|
||||
recv.get("checkpoint/registerHeight", &p.checkpointNumber)
|
||||
|
||||
if !p.onlyAnnounce {
|
||||
for msgCode := range reqAvgTimeCost {
|
||||
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)
|
||||
p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)])
|
||||
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 {
|
||||
p.server = recv.get("flowControl/MRR", nil) == nil
|
||||
if p.server {
|
||||
|
||||
@@ -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.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config)
|
||||
srv.oracle = srv.setupOracle(node, e.BlockChain().Genesis().Hash(), config)
|
||||
|
||||
// Initialize the bloom trie indexer.
|
||||
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.SetDefaultFactors(defaultPosFactors, defaultNegFactors)
|
||||
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())
|
||||
|
||||
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 {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "les",
|
||||
Service: NewLightAPI(&s.lesCommons),
|
||||
},
|
||||
{
|
||||
Namespace: "les",
|
||||
Service: NewLightServerAPI(s),
|
||||
|
||||
+1
-149
@@ -17,76 +17,14 @@
|
||||
package les
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/les/downloader"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
"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.
|
||||
func (h *clientHandler) synchronise(peer *serverPeer) {
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
// Recap the checkpoint. The light client may be connected to several different
|
||||
// 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 = ¶ms.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).
|
||||
// Notify testing framework if syncing has completed (for testing purpose).
|
||||
defer func() {
|
||||
if h.syncEnd != nil {
|
||||
h.syncEnd(h.backend.blockchain.CurrentHeader())
|
||||
}
|
||||
}()
|
||||
|
||||
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 {
|
||||
h.syncStart(h.backend.blockchain.CurrentHeader())
|
||||
}
|
||||
|
||||
+2
-309
@@ -18,30 +18,18 @@ package les
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// Test light syncing which will download all headers from genesis.
|
||||
func 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
|
||||
// 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) {
|
||||
func testSyncing(t *testing.T, protocol int) {
|
||||
config := light.TestServerIndexerConfig
|
||||
|
||||
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
|
||||
|
||||
// Checkpoint syncing or legacy checkpoint syncing.
|
||||
if syncMode == 1 || syncMode == 2 {
|
||||
// Assemble checkpoint 0
|
||||
s, _, head := server.chtIndexer.Sections()
|
||||
cp := ¶ms.TrustedCheckpoint{
|
||||
SectionIndex: 0,
|
||||
SectionHead: head,
|
||||
CHTRoot: light.GetChtRoot(server.db, s-1, head),
|
||||
BloomRoot: light.GetBloomTrieRoot(server.db, s-1, head),
|
||||
}
|
||||
if syncMode == 1 {
|
||||
// Register the assembled checkpoint as hardcoded one.
|
||||
client.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)
|
||||
client.handler.syncEnd = func(header *types.Header) {
|
||||
if header.Number.Uint64() == expected {
|
||||
@@ -133,258 +81,3 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
|
||||
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 := ¶ms.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 := ¶ms.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")
|
||||
}
|
||||
}
|
||||
|
||||
+5
-63
@@ -29,13 +29,11 @@ import (
|
||||
"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/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"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/forkid"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
@@ -45,7 +43,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/les/checkpointoracle"
|
||||
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||
vfs "github.com/ethereum/go-ethereum/les/vflux/server"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
@@ -72,18 +69,11 @@ var (
|
||||
testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029")
|
||||
|
||||
// Checkpoint oracle relative fields
|
||||
oracleAddr common.Address
|
||||
signerKey, _ = crypto.GenerateKey()
|
||||
signerAddr = crypto.PubkeyToAddress(signerKey.PublicKey)
|
||||
)
|
||||
|
||||
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.
|
||||
testBufLimit = uint64(1000000)
|
||||
|
||||
@@ -117,11 +107,7 @@ func prepare(n int, backend *backends.SimulatedBackend) {
|
||||
case 0:
|
||||
// Builtin-block
|
||||
// number: 1
|
||||
// txs: 2
|
||||
|
||||
// deploy checkpoint contract
|
||||
auth, _ := bind.NewKeyedTransactorWithChainID(bankKey, big.NewInt(1337))
|
||||
oracleAddr, _, _, _ = contract.DeployCheckpointOracle(auth, backend, []common.Address{signerAddr}, sectionSize, processConfirms, big.NewInt(1))
|
||||
// txs: 1
|
||||
|
||||
// bankUser transfers some ether to user1
|
||||
nonce, _ := backend.PendingNonceAt(ctx, bankAddr)
|
||||
@@ -201,28 +187,10 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index
|
||||
GasLimit: 100000000,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
oracle *checkpointoracle.CheckpointOracle
|
||||
)
|
||||
genesis := gspec.MustCommit(db)
|
||||
chain, _ := light.NewLightChain(odr, gspec.Config, engine, nil)
|
||||
if indexers != nil {
|
||||
checkpointConfig := ¶ms.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)
|
||||
}
|
||||
chain, _ := light.NewLightChain(odr, gspec.Config, engine)
|
||||
|
||||
client := &LightEthereum{
|
||||
lesCommons: lesCommons{
|
||||
genesis: genesis.Hash(),
|
||||
@@ -230,7 +198,6 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index
|
||||
chainConfig: params.AllEthashProtocolChanges,
|
||||
iConfig: light.TestClientIndexerConfig,
|
||||
chainDb: db,
|
||||
oracle: oracle,
|
||||
chainReader: chain,
|
||||
closeCh: make(chan struct{}),
|
||||
},
|
||||
@@ -243,11 +210,8 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index
|
||||
eventMux: evmux,
|
||||
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()
|
||||
return client.handler, func() {
|
||||
client.handler.stop()
|
||||
@@ -262,7 +226,6 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
|
||||
GasLimit: 100000000,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
oracle *checkpointoracle.CheckpointOracle
|
||||
)
|
||||
genesis := gspec.MustCommit(db)
|
||||
|
||||
@@ -273,24 +236,7 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
|
||||
txpoolConfig := txpool.DefaultConfig
|
||||
txpoolConfig.Journal = ""
|
||||
txpool := txpool.NewTxPool(txpoolConfig, gspec.Config, simulation.Blockchain())
|
||||
if indexers != nil {
|
||||
checkpointConfig := ¶ms.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{
|
||||
lesCommons: lesCommons{
|
||||
genesis: genesis.Hash(),
|
||||
@@ -299,7 +245,6 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
|
||||
iConfig: light.TestServerIndexerConfig,
|
||||
chainDb: db,
|
||||
chainReader: simulation.Blockchain(),
|
||||
oracle: oracle,
|
||||
closeCh: make(chan struct{}),
|
||||
},
|
||||
peers: newClientPeerSet(),
|
||||
@@ -316,9 +261,6 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da
|
||||
server.clientPool.Start()
|
||||
server.clientPool.SetLimits(10000, 10000) // Assign enough capacity for clientpool
|
||||
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.handler.start()
|
||||
closer := func() { server.Stop() }
|
||||
|
||||
Reference in New Issue
Block a user