diff options
Diffstat (limited to 'lds/odr_requests.go')
-rw-r--r-- | lds/odr_requests.go | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/lds/odr_requests.go b/lds/odr_requests.go new file mode 100644 index 000000000..6146f53fc --- /dev/null +++ b/lds/odr_requests.go @@ -0,0 +1,580 @@ +// 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 <http://www.gnu.org/licenses/>. + +// Package light implements on-demand retrieval capable state and chain objects +// for the Ethereum Light Client. +package lds + +import ( + "encoding/binary" + "errors" + "fmt" + + "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/core/rawdb" + "github.com/dexon-foundation/dexon/core/types" + "github.com/dexon-foundation/dexon/crypto" + "github.com/dexon-foundation/dexon/ethdb" + "github.com/dexon-foundation/dexon/light" + "github.com/dexon-foundation/dexon/log" + "github.com/dexon-foundation/dexon/rlp" + "github.com/dexon-foundation/dexon/trie" +) + +var ( + errInvalidMessageType = errors.New("invalid message type") + errInvalidEntryCount = errors.New("invalid number of 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") + errCHTNumberMismatch = errors.New("cht number mismatch") + errUselessNodes = errors.New("useless nodes in merkle proof nodeset") +) + +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) + case *light.BloomRequest: + return (*BloomRequest)(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, false) +} + +// 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 errInvalidEntryCount + } + body := bodies[0] + + // Retrieve our stored header and validate block content against it + header := rawdb.ReadHeader(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, false) +} + +// 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 errInvalidEntryCount + } + receipt := receipts[0] + + // Retrieve our stored header and validate receipt content against it + header := rawdb.ReadHeader(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 { + switch peer.version { + case lpv1: + return peer.GetRequestCost(GetProofsV1Msg, 1) + case lpv2: + return peer.GetRequestCost(GetProofsV2Msg, 1) + default: + panic(nil) + } +} + +// 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, true) +} + +// 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) + + switch msg.MsgType { + case MsgProofsV1: + proofs := msg.Obj.([]light.NodeList) + if len(proofs) != 1 { + return errInvalidEntryCount + } + nodeSet := proofs[0].NodeSet() + // Verify the proof and store if checks out + if _, _, err := trie.VerifyProof(r.Id.Root, r.Key, nodeSet); err != nil { + return fmt.Errorf("merkle proof verification failed: %v", err) + } + r.Proof = nodeSet + return nil + + case MsgProofsV2: + proofs := msg.Obj.(light.NodeList) + // Verify the proof and store if checks out + nodeSet := proofs.NodeSet() + reads := &readTraceDB{db: nodeSet} + if _, _, err := trie.VerifyProof(r.Id.Root, r.Key, reads); err != nil { + return fmt.Errorf("merkle proof verification failed: %v", err) + } + // check if all nodes have been read by VerifyProof + if len(reads.reads) != nodeSet.KeyCount() { + return errUselessNodes + } + r.Proof = nodeSet + return nil + + default: + return errInvalidMessageType + } +} + +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, true) +} + +// 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 errInvalidEntryCount + } + 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 +} + +const ( + // helper trie type constants + htCanonical = iota // Canonical hash trie + htBloomBits // BloomBits trie + + // applicable for all helper trie requests + auxRoot = 1 + // applicable for htCanonical + auxHeader = 2 +) + +type HelperTrieReq struct { + Type uint + TrieIdx uint64 + Key []byte + FromLevel, AuxReq uint +} + +type HelperTrieResps struct { // describes all responses, not just a single one + Proofs light.NodeList + AuxData [][]byte +} + +// legacy LES/1 +type ChtReq struct { + ChtNum, BlockNum uint64 + FromLevel uint +} + +// legacy LES/1 +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 { + switch peer.version { + case lpv1: + return peer.GetRequestCost(GetHeaderProofsMsg, 1) + case lpv2: + return peer.GetRequestCost(GetHelperTrieProofsMsg, 1) + default: + panic(nil) + } +} + +// 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 peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize +} + +// 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) + var encNum [8]byte + binary.BigEndian.PutUint64(encNum[:], r.BlockNum) + req := 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") + } + blockNum := binary.BigEndian.Uint64(req.Key) + // convert HelperTrie request to old CHT request + reqsV1 = ChtReq{ChtNum: (req.TrieIdx + 1) * (r.Config.ChtSize / r.Config.PairChtSize), BlockNum: blockNum, FromLevel: req.FromLevel} + return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), []ChtReq{reqsV1}) + case lpv2: + return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), []HelperTrieReq{req}) + default: + panic(nil) + } +} + +// 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) + + switch msg.MsgType { + case MsgHeaderProofs: // LES/1 backwards compatibility + proofs := msg.Obj.([]ChtResp) + if len(proofs) != 1 { + return errInvalidEntryCount + } + proof := proofs[0] + + // 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: + resp := msg.Obj.(HelperTrieResps) + if len(resp.AuxData) != 1 { + return errInvalidEntryCount + } + nodeSet := resp.Proofs.NodeSet() + headerEnc := resp.AuxData[0] + if len(headerEnc) == 0 { + return errHeaderUnavailable + } + 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.Proof = nodeSet + r.Td = node.Td + default: + return errInvalidMessageType + } + return nil +} + +type BloomReq struct { + BloomTrieNum, BitIdx, SectionIndex, FromLevel uint64 +} + +// ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface +type BloomRequest light.BloomRequest + +// GetCost returns the cost of the given ODR request according to the serving +// peer's cost table (implementation of LesOdrRequest) +func (r *BloomRequest) GetCost(peer *peer) uint64 { + return peer.GetRequestCost(GetHelperTrieProofsMsg, len(r.SectionIndexList)) +} + +// CanSend tells if a certain peer is suitable for serving the given request +func (r *BloomRequest) CanSend(peer *peer) bool { + peer.lock.RLock() + defer peer.lock.RUnlock() + + if peer.version < lpv2 { + return false + } + return peer.headInfo.Number >= r.Config.BloomTrieConfirms && r.BloomTrieNum <= (peer.headInfo.Number-r.Config.BloomTrieConfirms)/r.Config.BloomTrieSize +} + +// Request sends an ODR request to the LES network (implementation of LesOdrRequest) +func (r *BloomRequest) Request(reqID uint64, peer *peer) error { + peer.Log().Debug("Requesting BloomBits", "bloomTrie", r.BloomTrieNum, "bitIdx", r.BitIdx, "sections", r.SectionIndexList) + reqs := make([]HelperTrieReq, len(r.SectionIndexList)) + + var encNumber [10]byte + binary.BigEndian.PutUint16(encNumber[:2], uint16(r.BitIdx)) + + for i, sectionIdx := range r.SectionIndexList { + binary.BigEndian.PutUint64(encNumber[2:], sectionIdx) + reqs[i] = HelperTrieReq{ + Type: htBloomBits, + TrieIdx: r.BloomTrieNum, + Key: common.CopyBytes(encNumber[:]), + } + } + return peer.RequestHelperTrieProofs(reqID, r.GetCost(peer), reqs) +} + +// 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 *BloomRequest) Validate(db ethdb.Database, msg *Msg) error { + log.Debug("Validating BloomBits", "bloomTrie", r.BloomTrieNum, "bitIdx", r.BitIdx, "sections", r.SectionIndexList) + + // Ensure we have a correct message with a single proof element + if msg.MsgType != MsgHelperTrieProofs { + return errInvalidMessageType + } + resps := msg.Obj.(HelperTrieResps) + proofs := resps.Proofs + nodeSet := proofs.NodeSet() + reads := &readTraceDB{db: nodeSet} + + r.BloomBits = make([][]byte, len(r.SectionIndexList)) + + // Verify the proofs + var encNumber [10]byte + binary.BigEndian.PutUint16(encNumber[:2], uint16(r.BitIdx)) + + for i, idx := range r.SectionIndexList { + binary.BigEndian.PutUint64(encNumber[2:], idx) + value, _, err := trie.VerifyProof(r.BloomTrieRoot, encNumber[:], reads) + if err != nil { + return err + } + r.BloomBits[i] = value + } + + if len(reads.reads) != nodeSet.KeyCount() { + return errUselessNodes + } + r.Proofs = nodeSet + return nil +} + +// readTraceDB stores the keys of database reads. We use this to check that received node +// sets contain only the trie nodes necessary to make proofs pass. +type readTraceDB struct { + db trie.DatabaseReader + reads map[string]struct{} +} + +// Get returns a stored node +func (db *readTraceDB) Get(k []byte) ([]byte, error) { + if db.reads == nil { + db.reads = make(map[string]struct{}) + } + db.reads[string(k)] = struct{}{} + return db.db.Get(k) +} + +// Has returns true if the node set contains the given key +func (db *readTraceDB) Has(key []byte) (bool, error) { + _, err := db.Get(key) + return err == nil, nil +} |