diff --git a/les/odr_requests.go b/les/odr_requests.go index 96bd5a488a..b9ed1fa5ec 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -22,6 +22,7 @@ import ( "encoding/binary" "errors" "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" @@ -352,9 +353,9 @@ type ChtRequest light.ChtRequest func (r *ChtRequest) GetCost(peer *peer) uint64 { switch peer.version { case lpv1: - return peer.GetRequestCost(GetHeaderProofsMsg, 1) + return peer.GetRequestCost(GetHeaderProofsMsg, len(r.BlockNum)) case lpv2: - return peer.GetRequestCost(GetHelperTrieProofsMsg, 1) + return peer.GetRequestCost(GetHelperTrieProofsMsg, len(r.BlockNum)) default: panic(nil) } @@ -371,26 +372,33 @@ func (r *ChtRequest) CanSend(peer *peer, config *light.IndexerConfig) bool { // Request sends an ODR request to the LES network (implementation of LesOdrRequest) func (r *ChtRequest) Request(reqID uint64, peer *peer, config *light.IndexerConfig) error { peer.Log().Debug("Requesting CHT", "cht", r.ChtNum, "block", r.BlockNum) - var encNum [8]byte - binary.BigEndian.PutUint64(encNum[:], r.BlockNum) - req := HelperTrieReq{ - Type: htCanonical, - TrieIdx: r.ChtNum, - Key: encNum[:], - AuxReq: auxHeader, + var ( + encNum [8]byte + reqs []HelperTrieReq + ) + for _, num := range r.BlockNum { + binary.BigEndian.PutUint64(encNum[:], num) + reqs = append(reqs, HelperTrieReq{ + Type: htCanonical, + TrieIdx: r.ChtNum, + Key: encNum[:], + AuxReq: auxHeader, + }) } switch peer.version { case lpv1: - var reqsV1 ChtReq - if req.Type != htCanonical || req.AuxReq != auxHeader || len(req.Key) != 8 { - return fmt.Errorf("Request invalid in LES/1 mode") + var reqsV1 []ChtReq + for _, r := range reqs { + if r.Type != htCanonical || r.AuxReq != auxHeader || len(r.Key) != 8 { + return fmt.Errorf("Request invalid in LES/1 mode") + } + blockNum := binary.BigEndian.Uint64(r.Key) + // convert HelperTrie request to old CHT request + reqsV1 = append(reqsV1, ChtReq{ChtNum: (r.TrieIdx+1)*(config.ChtSize/config.PairChtSize), BlockNum: blockNum, FromLevel: r.FromLevel}) } - blockNum := binary.BigEndian.Uint64(req.Key) - // convert HelperTrie request to old CHT request - reqsV1 = ChtReq{ChtNum: (req.TrieIdx + 1) * (config.ChtSize / config.PairChtSize), BlockNum: blockNum, FromLevel: req.FromLevel} - return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), []ChtReq{reqsV1}) + return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), reqsV1) case lpv2: - return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), []HelperTrieReq{req}) + return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), reqs) default: panic(nil) } @@ -404,73 +412,88 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error { switch msg.MsgType { case MsgHeaderProofs: // LES/1 backwards compatibility - proofs := msg.Obj.([]ChtResp) - if len(proofs) != 1 { + resps := msg.Obj.([]ChtResp) + if len(resps) != len(r.BlockNum) { return errInvalidEntryCount } - proof := proofs[0] + var ( + headers []*types.Header + tds []*big.Int + encNumber [8]byte + node light.ChtNode + nodeset = light.NewNodeSet() + ) + for i, num := range r.BlockNum { + resp := resps[i] + // Verify the CHT + binary.BigEndian.PutUint64(encNumber[:], num) + value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], light.NodeList(resp.Proof).NodeSet()) + if err != nil { + return err + } + if err := rlp.DecodeBytes(value, &node); err != nil { + return err + } + if node.Hash != resp.Header.Hash() { + return errCHTHashMismatch + } + if num != resp.Header.Number.Uint64() { + return errCHTNumberMismatch + } + // Verifications passed, store temporarily + headers = append(headers, resp.Header) + tds = append(tds, node.Td) + light.NodeList(resp.Proof).Store(nodeset) + } + r.Header = headers + r.Td = tds + r.Proof = nodeset - // Verify the CHT - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) - - value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], light.NodeList(proof.Proof).NodeSet()) - if err != nil { - return err - } - var node light.ChtNode - if err := rlp.DecodeBytes(value, &node); err != nil { - return err - } - if node.Hash != proof.Header.Hash() { - return errCHTHashMismatch - } - // Verifications passed, store and return - r.Header = proof.Header - r.Proof = light.NodeList(proof.Proof).NodeSet() - r.Td = node.Td case MsgHelperTrieProofs: + // Check if the number of items in the response is the same as we requested. resp := msg.Obj.(HelperTrieResps) - if len(resp.AuxData) != 1 { + if len(resp.AuxData) != len(r.BlockNum) { return errInvalidEntryCount } - nodeSet := resp.Proofs.NodeSet() - headerEnc := resp.AuxData[0] - if len(headerEnc) == 0 { - return errHeaderUnavailable + var ( + headers []*types.Header + tds []*big.Int + encNumber [8]byte + node light.ChtNode + nodeSet = resp.Proofs.NodeSet() + ) + for i, num := range r.BlockNum { + enc := resp.AuxData[i] + if len(enc) == 0 { + return errHeaderUnavailable + } + header := new(types.Header) + if err := rlp.DecodeBytes(enc, header); err != nil { + return errHeaderUnavailable + } + // Verify the CHT + binary.BigEndian.PutUint64(encNumber[:], num) + value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], nodeSet) + if err != nil { + return fmt.Errorf("merkle proof verification failed: %v", err) + } + if err := rlp.DecodeBytes(value, &node); err != nil { + return err + } + if node.Hash != header.Hash() { + return errCHTHashMismatch + } + if num != header.Number.Uint64() { + return errCHTNumberMismatch + } + // Verifications passed, store temporarily + headers = append(headers, header) + tds = append(tds, node.Td) } - header := new(types.Header) - if err := rlp.DecodeBytes(headerEnc, header); err != nil { - return errHeaderUnavailable - } - - // Verify the CHT - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) - - reads := &readTraceDB{db: nodeSet} - value, _, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads) - if err != nil { - return fmt.Errorf("merkle proof verification failed: %v", err) - } - if len(reads.reads) != nodeSet.KeyCount() { - return errUselessNodes - } - - var node light.ChtNode - if err := rlp.DecodeBytes(value, &node); err != nil { - return err - } - if node.Hash != header.Hash() { - return errCHTHashMismatch - } - if r.BlockNum != header.Number.Uint64() { - return errCHTNumberMismatch - } - // Verifications passed, store and return - r.Header = header + r.Header = headers + r.Td = tds r.Proof = nodeSet - r.Td = node.Td + default: return errInvalidMessageType } diff --git a/light/odr.go b/light/odr.go index aeefbe9395..9e6b6d3fe4 100644 --- a/light/odr.go +++ b/light/odr.go @@ -133,20 +133,26 @@ func (req *ReceiptsRequest) StoreResult(db ethdb.Database, config *IndexerConfig // ChtRequest is the ODR request type for state/storage trie entries type ChtRequest struct { OdrRequest - ChtNum, BlockNum uint64 - ChtRoot common.Hash - Header *types.Header - Td *big.Int - Proof *NodeSet + ChtNum uint64 + ChtRoot common.Hash + BlockNum []uint64 + Header []*types.Header + Td []*big.Int + Proof *NodeSet } // StoreResult stores the retrieved data in local database func (req *ChtRequest) StoreResult(db ethdb.Database, config *IndexerConfig) { - hash, num := req.Header.Hash(), req.Header.Number.Uint64() + // The block number, header, td, proof length consistency has been verified + // in the validation phase. + for index := range req.BlockNum { + header := req.Header[index] + hash, num := header.Hash(), header.Number.Uint64() - rawdb.WriteHeader(db, req.Header) - rawdb.WriteTd(db, hash, num, req.Td) - rawdb.WriteCanonicalHash(db, hash, num) + rawdb.WriteHeader(db, header) + rawdb.WriteTd(db, hash, num, req.Td[index]) + rawdb.WriteCanonicalHash(db, hash, num) + } } // BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure diff --git a/light/odr_util.go b/light/odr_util.go index ee509993c1..c54acc5dcf 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -62,11 +62,12 @@ func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*typ if number >= chtCount*odr.IndexerConfig().ChtSize { return nil, ErrNoTrustedCht } - r := &ChtRequest{ChtRoot: GetChtRoot(db, chtCount-1, sectionHead), ChtNum: chtCount - 1, BlockNum: number} + r := &ChtRequest{ChtRoot: GetChtRoot(db, chtCount-1, sectionHead), ChtNum: chtCount - 1, BlockNum: []uint64{number}} if err := odr.Retrieve(ctx, r); err != nil { return nil, err } - return r.Header, nil + // Response item number has been checked in validation stage, no extra checking needed. + return r.Header[0], nil } func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) {