diff options
author | gary rong <garyrong0905@gmail.com> | 2019-06-28 15:34:02 +0800 |
---|---|---|
committer | Péter Szilágyi <peterke@gmail.com> | 2019-06-28 15:34:02 +0800 |
commit | f7cdea2bdcd7ff3cec99731cb912cde0b233d6c9 (patch) | |
tree | b463c8dd42547edceb778d946927d2c363303324 /les/sync.go | |
parent | 702f52fb99d60b4b6bab05799c14dafdd8648854 (diff) | |
download | go-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.go | 157 |
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))) } |