// 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 light implements on-demand retrieval capable state and chain objects // for the Ethereum Light Client. package les import ( "encoding/binary" "errors" "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) var ( errInvalidMessageType = errors.New("invalid message type") errMultipleEntries = errors.New("multiple response entries") errHeaderUnavailable = errors.New("header unavailable") errTxHashMismatch = errors.New("transaction hash mismatch") errUncleHashMismatch = errors.New("uncle hash mismatch") errReceiptHashMismatch = errors.New("receipt hash mismatch") errDataHashMismatch = errors.New("data hash mismatch") errCHTHashMismatch = errors.New("cht hash mismatch") ) type LesOdrRequest interface { GetCost(*peer) uint64 CanSend(*peer) bool Request(uint64, *peer) error Validate(ethdb.Database, *Msg) error } func LesRequest(req light.OdrRequest) LesOdrRequest { switch r := req.(type) { case *light.BlockRequest: return (*BlockRequest)(r) case *light.ReceiptsRequest: return (*ReceiptsRequest)(r) case *light.TrieRequest: return (*TrieRequest)(r) case *light.CodeRequest: return (*CodeRequest)(r) case *light.ChtRequest: return (*ChtRequest)(r) default: return nil } } // BlockRequest is the ODR request type for block bodies type BlockRequest light.BlockRequest // GetCost returns the cost of the given ODR request according to the serving // peer's cost table (implementation of LesOdrRequest) func (r *BlockRequest) GetCost(peer *peer) uint64 { return peer.GetRequestCost(GetBlockBodiesMsg, 1) } // CanSend tells if a certain peer is suitable for serving the given request func (r *BlockRequest) CanSend(peer *peer) bool { return peer.HasBlock(r.Hash, r.Number) } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) func (r *BlockRequest) Request(reqID uint64, peer *peer) error { peer.Log().Debug("Requesting block body", "hash", r.Hash) return peer.RequestBodies(reqID, r.GetCost(peer), []common.Hash{r.Hash}) } // Valid processes an ODR request reply message from the LES network // returns true and stores results in memory if the message was a valid reply // to the request (implementation of LesOdrRequest) func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error { log.Debug("Validating block body", "hash", r.Hash) // Ensure we have a correct message with a single block body if msg.MsgType != MsgBlockBodies { return errInvalidMessageType } bodies := msg.Obj.([]*types.Body) if len(bodies) != 1 { return errMultipleEntries } body := bodies[0] // Retrieve our stored header and validate block content against it header := core.GetHeader(db, r.Hash, r.Number) if header == nil { return errHeaderUnavailable } if header.TxHash != types.DeriveSha(types.Transactions(body.Transactions)) { return errTxHashMismatch } if header.UncleHash != types.CalcUncleHash(body.Uncles) { return errUncleHashMismatch } // Validations passed, encode and store RLP data, err := rlp.EncodeToBytes(body) if err != nil { return err } r.Rlp = data return nil } // ReceiptsRequest is the ODR request type for block receipts by block hash type ReceiptsRequest light.ReceiptsRequest // GetCost returns the cost of the given ODR request according to the serving // peer's cost table (implementation of LesOdrRequest) func (r *ReceiptsRequest) GetCost(peer *peer) uint64 { return peer.GetRequestCost(GetReceiptsMsg, 1) } // CanSend tells if a certain peer is suitable for serving the given request func (r *ReceiptsRequest) CanSend(peer *peer) bool { return peer.HasBlock(r.Hash, r.Number) } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) func (r *ReceiptsRequest) Request(reqID uint64, peer *peer) error { peer.Log().Debug("Requesting block receipts", "hash", r.Hash) return peer.RequestReceipts(reqID, r.GetCost(peer), []common.Hash{r.Hash}) } // Valid processes an ODR request reply message from the LES network // returns true and stores results in memory if the message was a valid reply // to the request (implementation of LesOdrRequest) func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error { log.Debug("Validating block receipts", "hash", r.Hash) // Ensure we have a correct message with a single block receipt if msg.MsgType != MsgReceipts { return errInvalidMessageType } receipts := msg.Obj.([]types.Receipts) if len(receipts) != 1 { return errMultipleEntries } receipt := receipts[0] // Retrieve our stored header and validate receipt content against it header := core.GetHeader(db, r.Hash, r.Number) if header == nil { return errHeaderUnavailable } if header.ReceiptHash != types.DeriveSha(receipt) { return errReceiptHashMismatch } // Validations passed, store and return r.Receipts = receipt return nil } type ProofReq struct { BHash common.Hash AccKey, Key []byte FromLevel uint } // ODR request type for state/storage trie entries, see LesOdrRequest interface type TrieRequest light.TrieRequest // GetCost returns the cost of the given ODR request according to the serving // peer's cost table (implementation of LesOdrRequest) func (r *TrieRequest) GetCost(peer *peer) uint64 { return peer.GetRequestCost(GetProofsMsg, 1) } // CanSend tells if a certain peer is suitable for serving the given request func (r *TrieRequest) CanSend(peer *peer) bool { return peer.HasBlock(r.Id.BlockHash, r.Id.BlockNumber) } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) func (r *TrieRequest) Request(reqID uint64, peer *peer) error { peer.Log().Debug("Requesting trie proof", "root", r.Id.Root, "key", r.Key) req := &ProofReq{ BHash: r.Id.BlockHash, AccKey: r.Id.AccKey, Key: r.Key, } return peer.RequestProofs(reqID, r.GetCost(peer), []*ProofReq{req}) } // Valid processes an ODR request reply message from the LES network // returns true and stores results in memory if the message was a valid reply // to the request (implementation of LesOdrRequest) func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error { log.Debug("Validating trie proof", "root", r.Id.Root, "key", r.Key) // Ensure we have a correct message with a single proof if msg.MsgType != MsgProofs { return errInvalidMessageType } proofs := msg.Obj.([][]rlp.RawValue) if len(proofs) != 1 { return errMultipleEntries } // Verify the proof and store if checks out if _, err := trie.VerifyProof(r.Id.Root, r.Key, proofs[0]); err != nil { return fmt.Errorf("merkle proof verification failed: %v", err) } r.Proof = proofs[0] return nil } type CodeReq struct { BHash common.Hash AccKey []byte } // ODR request type for node data (used for retrieving contract code), see LesOdrRequest interface type CodeRequest light.CodeRequest // GetCost returns the cost of the given ODR request according to the serving // peer's cost table (implementation of LesOdrRequest) func (r *CodeRequest) GetCost(peer *peer) uint64 { return peer.GetRequestCost(GetCodeMsg, 1) } // CanSend tells if a certain peer is suitable for serving the given request func (r *CodeRequest) CanSend(peer *peer) bool { return peer.HasBlock(r.Id.BlockHash, r.Id.BlockNumber) } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) func (r *CodeRequest) Request(reqID uint64, peer *peer) error { peer.Log().Debug("Requesting code data", "hash", r.Hash) req := &CodeReq{ BHash: r.Id.BlockHash, AccKey: r.Id.AccKey, } return peer.RequestCode(reqID, r.GetCost(peer), []*CodeReq{req}) } // Valid processes an ODR request reply message from the LES network // returns true and stores results in memory if the message was a valid reply // to the request (implementation of LesOdrRequest) func (r *CodeRequest) Validate(db ethdb.Database, msg *Msg) error { log.Debug("Validating code data", "hash", r.Hash) // Ensure we have a correct message with a single code element if msg.MsgType != MsgCode { return errInvalidMessageType } reply := msg.Obj.([][]byte) if len(reply) != 1 { return errMultipleEntries } data := reply[0] // Verify the data and store if checks out if hash := crypto.Keccak256Hash(data); r.Hash != hash { return errDataHashMismatch } r.Data = data return nil } type ChtReq struct { ChtNum, BlockNum, FromLevel uint64 } type ChtResp struct { Header *types.Header Proof []rlp.RawValue } // ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface type ChtRequest light.ChtRequest // GetCost returns the cost of the given ODR request according to the serving // peer's cost table (implementation of LesOdrRequest) func (r *ChtRequest) GetCost(peer *peer) uint64 { return peer.GetRequestCost(GetHeaderProofsMsg, 1) } // CanSend tells if a certain peer is suitable for serving the given request func (r *ChtRequest) CanSend(peer *peer) bool { peer.lock.RLock() defer peer.lock.RUnlock() return r.ChtNum <= (peer.headInfo.Number-light.ChtConfirmations)/light.ChtFrequency } // Request sends an ODR request to the LES network (implementation of LesOdrRequest) func (r *ChtRequest) Request(reqID uint64, peer *peer) error { peer.Log().Debug("Requesting CHT", "cht", r.ChtNum, "block", r.BlockNum) req := &ChtReq{ ChtNum: r.ChtNum, BlockNum: r.BlockNum, } return peer.RequestHeaderProofs(reqID, r.GetCost(peer), []*ChtReq{req}) } // Valid processes an ODR request reply message from the LES network // returns true and stores results in memory if the message was a valid reply // to the request (implementation of LesOdrRequest) func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error { log.Debug("Validating CHT", "cht", r.ChtNum, "block", r.BlockNum) // Ensure we have a correct message with a single proof element if msg.MsgType != MsgHeaderProofs { return errInvalidMessageType } proofs := msg.Obj.([]ChtResp) if len(proofs) != 1 { return errMultipleEntries } proof := proofs[0] // Verify the CHT var encNumber [8]byte binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], proof.Proof) 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 = proof.Proof r.Td = node.Td return nil }