// Copyright 2016 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 . 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. if peer == nil { return } // Make sure the peer's TD is higher than our own. latest := h.backend.blockchain.CurrentHeader() currentTd := rawdb.ReadTd(h.backend.chainDb, latest.Hash(), latest.Number.Uint64()) 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). 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()) } // Fetch the remaining block headers based on the current chain header. if err := h.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))) }