From d8787230faa07e078a183765271313a7d2c6bdf2 Mon Sep 17 00:00:00 2001 From: Péter Szilágyi Date: Tue, 16 Apr 2019 13:20:38 +0300 Subject: eth, les, light: enforce CHT checkpoints on fast-sync too --- eth/handler.go | 77 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 39 deletions(-) (limited to 'eth/handler.go') diff --git a/eth/handler.go b/eth/handler.go index b42612a56..2c0ea1eb1 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" @@ -55,7 +54,7 @@ const ( ) var ( - daoChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the DAO handshake challenge + syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge ) // errIncompatibleConfig is returned if the requested protocols and configs are @@ -72,6 +71,9 @@ type ProtocolManager struct { fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks) 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 + txpool txPool blockchain *core.BlockChain chainconfig *params.ChainConfig @@ -126,6 +128,11 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne if mode == downloader.FastSync { manager.fastSync = uint32(1) } + // If we have trusted checkpoints, enforce them on the chain + if checkpoint, ok := params.TrustedCheckpoints[blockchain.Genesis().Hash()]; ok { + manager.checkpointNumber = (checkpoint.SectionIndex+1)*params.CHTFrequencyClient - 1 + manager.checkpointHash = checkpoint.SectionHead + } // Initiate a sub-protocol for every implemented version we can handle manager.SubProtocols = make([]p2p.Protocol, 0, len(ProtocolVersions)) for i, version := range ProtocolVersions { @@ -165,7 +172,7 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne return nil, errIncompatibleConfig } // Construct the different synchronisation mechanisms - manager.downloader = downloader.New(mode, chaindb, manager.eventMux, blockchain, nil, manager.removePeer) + manager.downloader = downloader.New(mode, manager.checkpointNumber, chaindb, manager.eventMux, blockchain, nil, manager.removePeer) validator := func(header *types.Header) error { return engine.VerifyHeader(blockchain, header, true) @@ -291,22 +298,22 @@ func (pm *ProtocolManager) handle(p *peer) error { // after this will be sent via broadcasts. pm.syncTransactions(p) - // If we're DAO hard-fork aware, validate any remote peer with regard to the hard-fork - if daoBlock := pm.chainconfig.DAOForkBlock; daoBlock != nil { - // Request the peer's DAO fork header for extra-data validation - if err := p.RequestHeadersByNumber(daoBlock.Uint64(), 1, 0, false); err != nil { + // If we have a trusted CHT, reject all peers below that (avoid fast sync eclipse) + if pm.checkpointHash != (common.Hash{}) { + // Request the peer's checkpoint header for chain height/weight validation + if err := p.RequestHeadersByNumber(pm.checkpointNumber, 1, 0, false); err != nil { return err } // Start a timer to disconnect if the peer doesn't reply in time - p.forkDrop = time.AfterFunc(daoChallengeTimeout, func() { - p.Log().Debug("Timed out DAO fork-check, dropping") + p.syncDrop = time.AfterFunc(syncChallengeTimeout, func() { + p.Log().Warn("Checkpoint challenge timed out, dropping", "addr", p.RemoteAddr(), "type", p.Name()) pm.removePeer(p.id) }) // Make sure it's cleaned up if the peer dies off defer func() { - if p.forkDrop != nil { - p.forkDrop.Stop() - p.forkDrop = nil + if p.syncDrop != nil { + p.syncDrop.Stop() + p.syncDrop = nil } }() } @@ -438,41 +445,33 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { if err := msg.Decode(&headers); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } - // If no headers were received, but we're expending a DAO fork check, maybe it's that - if len(headers) == 0 && p.forkDrop != nil { - // Possibly an empty reply to the fork header checks, sanity check TDs - verifyDAO := true - - // If we already have a DAO header, we can check the peer's TD against it. If - // the peer's ahead of this, it too must have a reply to the DAO check - if daoHeader := pm.blockchain.GetHeaderByNumber(pm.chainconfig.DAOForkBlock.Uint64()); daoHeader != nil { - if _, td := p.Head(); td.Cmp(pm.blockchain.GetTd(daoHeader.Hash(), daoHeader.Number.Uint64())) >= 0 { - verifyDAO = false - } - } - // If we're seemingly on the same chain, disable the drop timer - if verifyDAO { - p.Log().Debug("Seems to be on the same side of the DAO fork") - p.forkDrop.Stop() - p.forkDrop = nil - return nil + // If no headers were received, but we're expencting a checkpoint header, consider it that + if len(headers) == 0 && p.syncDrop != nil { + // Stop the timer either way, decide later to drop or not + p.syncDrop.Stop() + p.syncDrop = nil + + // If we're doing a fast 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(&pm.fastSync) == 1 { + p.Log().Warn("Dropping unsynced node during fast sync", "addr", p.RemoteAddr(), "type", p.Name()) + return errors.New("unsynced node cannot serve fast sync") } } // Filter out any explicitly requested headers, deliver the rest to the downloader filter := len(headers) == 1 if filter { - // If it's a potential DAO fork check, validate against the rules - if p.forkDrop != nil && pm.chainconfig.DAOForkBlock.Cmp(headers[0].Number) == 0 { - // Disable the fork drop timer - p.forkDrop.Stop() - p.forkDrop = nil + // If it's a potential sync progress check, validate the content and advertised chain weight + if p.syncDrop != nil && headers[0].Number.Uint64() == pm.checkpointNumber { + // Disable the sync drop timer + p.syncDrop.Stop() + p.syncDrop = nil // Validate the header and either drop the peer or continue - if err := misc.VerifyDAOHeaderExtraData(pm.chainconfig, headers[0]); err != nil { - p.Log().Debug("Verified to be on the other side of the DAO fork, dropping") - return err + if headers[0].Hash() != pm.checkpointHash { + return errors.New("checkpoint hash mismatch") } - p.Log().Debug("Verified to be on the same side of the DAO fork") return nil } // Otherwise if it's a whitelisted block, validate against the set -- cgit