aboutsummaryrefslogtreecommitdiffstats
path: root/les/sync.go
diff options
context:
space:
mode:
authorgary rong <garyrong0905@gmail.com>2019-06-28 15:34:02 +0800
committerPéter Szilágyi <peterke@gmail.com>2019-06-28 15:34:02 +0800
commitf7cdea2bdcd7ff3cec99731cb912cde0b233d6c9 (patch)
treeb463c8dd42547edceb778d946927d2c363303324 /les/sync.go
parent702f52fb99d60b4b6bab05799c14dafdd8648854 (diff)
downloadgo-tangerine-f7cdea2bdcd7ff3cec99731cb912cde0b233d6c9.tar
go-tangerine-f7cdea2bdcd7ff3cec99731cb912cde0b233d6c9.tar.gz
go-tangerine-f7cdea2bdcd7ff3cec99731cb912cde0b233d6c9.tar.bz2
go-tangerine-f7cdea2bdcd7ff3cec99731cb912cde0b233d6c9.tar.lz
go-tangerine-f7cdea2bdcd7ff3cec99731cb912cde0b233d6c9.tar.xz
go-tangerine-f7cdea2bdcd7ff3cec99731cb912cde0b233d6c9.tar.zst
go-tangerine-f7cdea2bdcd7ff3cec99731cb912cde0b233d6c9.zip
all: on-chain oracle checkpoint syncing (#19543)
* all: implement simple checkpoint syncing cmd, les, node: remove callback mechanism cmd, node: remove callback definition les: simplify the registrar les: expose checkpoint rpc services in the light client les, light: don't store untrusted receipt cmd, contracts, les: discard stale checkpoint cmd, contracts/registrar: loose restriction of registeration cmd, contracts: add replay-protection all: off-chain multi-signature contract params: deploy checkpoint contract for rinkeby cmd/registrar: add raw signing mode for registrar cmd/registrar, contracts/registrar, les: fixed messages * cmd/registrar, contracts/registrar: fix lints * accounts/abi/bind, les: address comments * cmd, contracts, les, light, params: minor checkpoint sync cleanups * cmd, eth, les, light: move checkpoint config to config file * cmd, eth, les, params: address comments * eth, les, params: address comments * cmd: polish up the checkpoint admin CLI * cmd, contracts, params: deploy new version contract * cmd/checkpoint-admin: add another flag for clef mode signing * cmd, contracts, les: rename and regen checkpoint oracle with abigen
Diffstat (limited to 'les/sync.go')
-rw-r--r--les/sync.go157
1 files changed, 145 insertions, 12 deletions
diff --git a/les/sync.go b/les/sync.go
index 1ac645585..54fd81c2c 100644
--- a/les/sync.go
+++ b/les/sync.go
@@ -18,11 +18,29 @@ package les
import (
"context"
+ "errors"
"time"
+ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/light"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+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
)
// syncer is responsible for periodically synchronising with the network, both
@@ -54,26 +72,141 @@ func (pm *ProtocolManager) syncer() {
}
}
-func (pm *ProtocolManager) needToSync(peerHead blockInfo) bool {
- head := pm.blockchain.CurrentHeader()
- currentTd := rawdb.ReadTd(pm.chainDb, head.Hash(), head.Number.Uint64())
- return currentTd != nil && peerHead.Td.Cmp(currentTd) > 0
+// 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 (pm *ProtocolManager) validateCheckpoint(peer *peer) error {
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
+ defer cancel()
+
+ // Fetch the block header corresponding to the checkpoint registration.
+ cp := peer.checkpoint
+ header, err := light.GetUntrustedHeaderByNumber(ctx, pm.odr, peer.checkpointNumber, peer.id)
+ if err != nil {
+ return err
+ }
+ // Fetch block logs associated with the block header.
+ logs, err := light.GetUntrustedBlockLogs(ctx, pm.odr, header)
+ if err != nil {
+ return err
+ }
+ events := pm.reg.contract.LookupCheckpointEvents(logs, cp.SectionIndex, cp.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 := pm.reg.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 block chain with a remote peer.
+// synchronise tries to sync up our local chain with a remote peer.
func (pm *ProtocolManager) synchronise(peer *peer) {
- // Short circuit if no peers are available
+ // Short circuit if the peer is nil.
if peer == nil {
return
}
-
// Make sure the peer's TD is higher than our own.
- if !pm.needToSync(peer.headBlockInfo()) {
+ latest := pm.blockchain.CurrentHeader()
+ currentTd := rawdb.ReadTd(pm.chainDb, latest.Hash(), latest.Number.Uint64())
+ if currentTd != nil && peer.headBlockInfo().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 hardcoded checkpoint or empty checkpoint
+ // (2) New version server but simple checkpoint syncing is not enabled(e.g. mainnet, new testnet or private network)
+ // => Use hardcoded checkpoint or empty checkpoint
+ // (3) New version server but the provided stable checkpoint is even lower than the hardcoded one.
+ // => Use hardcoded checkpoint
+ // (4) New version server with valid and higher stable checkpoint
+ // => Use provided checkpoint
+ var checkpoint = &peer.checkpoint
+ var hardcoded bool
+ if pm.checkpoint != nil && pm.checkpoint.SectionIndex >= peer.checkpoint.SectionIndex {
+ checkpoint = pm.checkpoint // Use the hardcoded one.
+ hardcoded = true
+ }
+ // 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 hardcoded(recap with local hardcoded 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)*pm.iConfig.ChtSize-1:
+ mode = lightSync
+ log.Debug("Disable checkpoint syncing", "reason", "local chain beyond the checkpoint")
+ case hardcoded:
+ mode = legacyCheckpointSync
+ log.Debug("Disable checkpoint syncing", "reason", "checkpoint is hardcoded")
+ case pm.reg == nil || !pm.reg.isRunning():
+ 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() {
+ if pm.reg != nil && pm.reg.syncDoneHook != nil {
+ pm.reg.syncDoneHook()
+ }
+ }()
+ start := time.Now()
+ if mode == checkpointSync || mode == legacyCheckpointSync {
+ // Validate the advertised checkpoint
+ if mode == legacyCheckpointSync {
+ checkpoint = pm.checkpoint
+ } else if mode == checkpointSync {
+ if err := pm.validateCheckpoint(peer); err != nil {
+ log.Debug("Failed to validate checkpoint", "reason", err)
+ pm.removePeer(peer.id)
+ return
+ }
+ pm.blockchain.(*light.LightChain).AddTrustedCheckpoint(checkpoint)
+ }
+ log.Debug("Checkpoint syncing start", "peer", peer.id, "checkpoint", checkpoint.SectionIndex)
- ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
- defer cancel()
- pm.blockchain.(*light.LightChain).SyncCht(ctx)
- pm.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync)
+ // 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() && !pm.blockchain.(*light.LightChain).SyncCheckpoint(ctx, checkpoint) {
+ log.Debug("Sync checkpoint failed")
+ pm.removePeer(peer.id)
+ return
+ }
+ }
+ // Fetch the remaining block headers based on the current chain header.
+ if err := pm.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync); err != nil {
+ log.Debug("Synchronise failed", "reason", err)
+ return
+ }
+ log.Debug("Synchronise finished", "elapsed", common.PrettyDuration(time.Since(start)))
}