les: implement ProofsV2Msg

This commit is contained in:
Zsolt Felfoldi 2017-08-12 16:15:25 +02:00
parent 6016610f7d
commit c76e52132f
8 changed files with 168 additions and 51 deletions

View file

@ -18,6 +18,7 @@
package les package les
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
@ -56,7 +57,7 @@ const (
MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request
MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request
MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
MaxHeaderProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request MaxPPTProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request
MaxTxSend = 64 // Amount of transactions to be send per request MaxTxSend = 64 // Amount of transactions to be send per request
disableClientRemovePeer = false disableClientRemovePeer = false
@ -316,7 +317,7 @@ func (pm *ProtocolManager) handle(p *peer) error {
} }
} }
var reqList = []uint64{GetBlockHeadersMsg, GetBlockBodiesMsg, GetCodeMsg, GetReceiptsMsg, GetProofsMsg, SendTxMsg, GetHeaderProofsMsg} var reqList = []uint64{GetBlockHeadersMsg, GetBlockBodiesMsg, GetCodeMsg, GetReceiptsMsg, GetProofsV1Msg, SendTxMsg, GetHeaderProofsMsg, GetProofsV2Msg, GetPPTProofsMsg}
// handleMsg is invoked whenever an inbound message is received from a remote // handleMsg is invoked whenever an inbound message is received from a remote
// peer. The remote connection is torn down upon returning any error. // peer. The remote connection is torn down upon returning any error.
@ -656,7 +657,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
Obj: resp.Receipts, Obj: resp.Receipts,
} }
case GetProofsMsg: case GetProofsV1Msg:
p.Log().Trace("Received proofs request") p.Log().Trace("Received proofs request")
// Decode the retrieval message // Decode the retrieval message
var req struct { var req struct {
@ -703,7 +704,67 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost) pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
return p.SendProofs(req.ReqID, bv, proofs) return p.SendProofs(req.ReqID, bv, proofs)
case ProofsMsg: case GetProofsV2Msg:
p.Log().Trace("Received les/2 proofs request")
// Decode the retrieval message
var req struct {
ReqID uint64
Reqs []ProofReq
}
if err := msg.Decode(&req); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
// Gather state data until the fetch or network limits is reached
var (
lastBHash common.Hash
lastAccKey []byte
tr, str *trie.Trie
)
reqCnt := len(req.Reqs)
if reject(uint64(reqCnt), MaxProofsFetch) {
return errResp(ErrRequestRejected, "")
}
nodes := light.NewNodeSet()
for _, req := range req.Reqs {
if nodes.DataSize() >= softResponseLimit {
break
}
if tr == nil || req.BHash != lastBHash {
if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil {
tr, _ = trie.New(header.Root, pm.chainDb)
} else {
tr = nil
}
lastBHash = req.BHash
str = nil
}
if tr != nil {
if len(req.AccKey) > 0 {
if str == nil || !bytes.Equal(req.AccKey, lastAccKey) {
sdata := tr.Get(req.AccKey)
str = nil
var acc state.Account
if err := rlp.DecodeBytes(sdata, &acc); err == nil {
str, _ = trie.New(acc.Root, pm.chainDb)
}
lastAccKey = common.CopyBytes(req.AccKey)
}
if str != nil {
str.Prove(req.Key, req.FromLevel, nodes)
}
} else {
tr.Prove(req.Key, req.FromLevel, nodes)
}
}
}
proofs := nodes.NodeList()
bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost)
pm.server.fcCostStats.update(msg.Code, uint64(reqCnt), rcost)
return p.SendProofsV2(req.ReqID, bv, proofs)
case ProofsV1Msg:
if pm.odr == nil { if pm.odr == nil {
return errResp(ErrUnexpectedResponse, "") return errResp(ErrUnexpectedResponse, "")
} }
@ -712,14 +773,35 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
// A batch of merkle proofs arrived to one of our previous requests // A batch of merkle proofs arrived to one of our previous requests
var resp struct { var resp struct {
ReqID, BV uint64 ReqID, BV uint64
Data [][]rlp.RawValue Data []light.NodeList
} }
if err := msg.Decode(&resp); err != nil { if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err) return errResp(ErrDecode, "msg %v: %v", msg, err)
} }
p.fcServer.GotReply(resp.ReqID, resp.BV) p.fcServer.GotReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{ deliverMsg = &Msg{
MsgType: MsgProofs, MsgType: MsgProofsV1,
ReqID: resp.ReqID,
Obj: resp.Data,
}
case ProofsV2Msg:
if pm.odr == nil {
return errResp(ErrUnexpectedResponse, "")
}
p.Log().Trace("Received les/2 proofs response")
// A batch of merkle proofs arrived to one of our previous requests
var resp struct {
ReqID, BV uint64
Data light.NodeList
}
if err := msg.Decode(&resp); err != nil {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
p.fcServer.GotReply(resp.ReqID, resp.BV)
deliverMsg = &Msg{
MsgType: MsgProofsV2,
ReqID: resp.ReqID, ReqID: resp.ReqID,
Obj: resp.Data, Obj: resp.Data,
} }
@ -740,7 +822,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
proofs []ChtResp proofs []ChtResp
) )
reqCnt := len(req.Reqs) reqCnt := len(req.Reqs)
if reject(uint64(reqCnt), MaxHeaderProofsFetch) { if reject(uint64(reqCnt), MaxPPTProofsFetch) {
return errResp(ErrRequestRejected, "") return errResp(ErrRequestRejected, "")
} }
for _, req := range req.Reqs { for _, req := range req.Reqs {

View file

@ -338,9 +338,9 @@ func testGetProofs(t *testing.T, protocol int) {
} }
} }
// Send the proof request and verify the response // Send the proof request and verify the response
cost := peer.GetRequestCost(GetProofsMsg, len(proofreqs)) cost := peer.GetRequestCost(GetProofsV1Msg, len(proofreqs))
sendRequest(peer.app, GetProofsMsg, 42, cost, proofreqs) sendRequest(peer.app, GetProofsV1Msg, 42, cost, proofreqs)
if err := expectResponse(peer.app, ProofsMsg, 42, testBufLimit, proofs); err != nil { if err := expectResponse(peer.app, ProofsV1Msg, 42, testBufLimit, proofs); err != nil {
t.Errorf("proofs mismatch: %v", err) t.Errorf("proofs mismatch: %v", err)
} }
} }

View file

@ -51,7 +51,8 @@ const (
MsgBlockBodies = iota MsgBlockBodies = iota
MsgCode MsgCode
MsgReceipts MsgReceipts
MsgProofs MsgProofsV1
MsgProofsV2
MsgHeaderProofs MsgHeaderProofs
) )

View file

@ -43,6 +43,7 @@ var (
errReceiptHashMismatch = errors.New("receipt hash mismatch") errReceiptHashMismatch = errors.New("receipt hash mismatch")
errDataHashMismatch = errors.New("data hash mismatch") errDataHashMismatch = errors.New("data hash mismatch")
errCHTHashMismatch = errors.New("cht hash mismatch") errCHTHashMismatch = errors.New("cht hash mismatch")
errUselessNodes = errors.New("useless nodes in merkle proof nodeset")
) )
type LesOdrRequest interface { type LesOdrRequest interface {
@ -186,7 +187,14 @@ type TrieRequest light.TrieRequest
// GetCost returns the cost of the given ODR request according to the serving // GetCost returns the cost of the given ODR request according to the serving
// peer's cost table (implementation of LesOdrRequest) // peer's cost table (implementation of LesOdrRequest)
func (r *TrieRequest) GetCost(peer *peer) uint64 { func (r *TrieRequest) GetCost(peer *peer) uint64 {
return peer.GetRequestCost(GetProofsMsg, 1) 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 // CanSend tells if a certain peer is suitable for serving the given request
@ -211,20 +219,38 @@ func (r *TrieRequest) Request(reqID uint64, peer *peer) error {
func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error { func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error {
log.Debug("Validating trie proof", "root", r.Id.Root, "key", r.Key) log.Debug("Validating trie proof", "root", r.Id.Root, "key", r.Key)
// Ensure we have a correct message with a single proof switch msg.MsgType {
if msg.MsgType != MsgProofs { case MsgProofsV1:
return errInvalidMessageType proofs := msg.Obj.([]light.NodeList)
}
proofs := msg.Obj.([][]rlp.RawValue)
if len(proofs) != 1 { if len(proofs) != 1 {
return errMultipleEntries return errInvalidEntryCount
} }
nodeSet := proofs[0].NodeSet()
// Verify the proof and store if checks out // Verify the proof and store if checks out
if _, err, _ := trie.VerifyProof(r.Id.Root, r.Key, light.NodeList(proofs[0]).NodeSet()); err != nil { if _, err, _ := trie.VerifyProof(r.Id.Root, r.Key, nodeSet); err != nil {
return fmt.Errorf("merkle proof verification failed: %v", err) return fmt.Errorf("merkle proof verification failed: %v", err)
} }
r.Proof = proofs[0] r.Proof = nodeSet
return nil return nil
case MsgProofsV2:
proofs := msg.Obj.(light.NodeList)
// Verify the proof and store if checks out
pdb := proofs.NodeSet()
cdb := pdb.ReadCache()
if _, err, _ := trie.VerifyProof(r.Id.Root, r.Key, cdb); err != nil {
return fmt.Errorf("merkle proof verification failed: %v", err)
}
// check if all nodes have been read by VerifyProof
if pdb.KeyCount() != cdb.KeyCount() {
return errUselessNodes
}
r.Proof = pdb
return nil
default:
return errInvalidMessageType
}
} }
type CodeReq struct { type CodeReq struct {

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/les/flowcontrol"
"github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -199,7 +200,12 @@ func (p *peer) SendReceiptsRLP(reqID, bv uint64, receipts []rlp.RawValue) error
// SendProofs sends a batch of merkle proofs, corresponding to the ones requested. // SendProofs sends a batch of merkle proofs, corresponding to the ones requested.
func (p *peer) SendProofs(reqID, bv uint64, proofs proofsData) error { func (p *peer) SendProofs(reqID, bv uint64, proofs proofsData) error {
return sendResponse(p.rw, ProofsMsg, reqID, bv, proofs) return sendResponse(p.rw, ProofsV1Msg, reqID, bv, proofs)
}
// SendProofsV2 sends a batch of merkle proofs, corresponding to the ones requested.
func (p *peer) SendProofsV2(reqID, bv uint64, proofs light.NodeList) error {
return sendResponse(p.rw, ProofsV2Msg, reqID, bv, proofs)
} }
// SendHeaderProofs sends a batch of header proofs, corresponding to the ones requested. // SendHeaderProofs sends a batch of header proofs, corresponding to the ones requested.
@ -244,7 +250,15 @@ func (p *peer) RequestReceipts(reqID, cost uint64, hashes []common.Hash) error {
// RequestProofs fetches a batch of merkle proofs from a remote node. // RequestProofs fetches a batch of merkle proofs from a remote node.
func (p *peer) RequestProofs(reqID, cost uint64, reqs []*ProofReq) error { func (p *peer) RequestProofs(reqID, cost uint64, reqs []*ProofReq) error {
p.Log().Debug("Fetching batch of proofs", "count", len(reqs)) p.Log().Debug("Fetching batch of proofs", "count", len(reqs))
return sendRequest(p.rw, GetProofsMsg, reqID, cost, reqs) switch p.version {
case lpv1:
return sendRequest(p.rw, GetProofsV1Msg, reqID, cost, reqs)
case lpv2:
return sendRequest(p.rw, GetProofsV2Msg, reqID, cost, reqs)
default:
panic(nil)
}
} }
// RequestHeaderProofs fetches a batch of header merkle proofs from a remote node. // RequestHeaderProofs fetches a batch of header merkle proofs from a remote node.

View file

@ -29,13 +29,14 @@ import (
// Constants to match up protocol versions and messages // Constants to match up protocol versions and messages
const ( const (
lpv1 = 1 lpv1 = 1
lpv2 = 2
) )
// Supported versions of the les protocol (first is primary). // Supported versions of the les protocol (first is primary).
var ProtocolVersions = []uint{lpv1} var ProtocolVersions = []uint{lpv1, lpv2}
// Number of implemented message corresponding to different protocol versions. // Number of implemented message corresponding to different protocol versions.
var ProtocolLengths = []uint64{15} var ProtocolLengths = []uint64{15, 19}
const ( const (
NetworkId = 1 NetworkId = 1
@ -53,13 +54,18 @@ const (
BlockBodiesMsg = 0x05 BlockBodiesMsg = 0x05
GetReceiptsMsg = 0x06 GetReceiptsMsg = 0x06
ReceiptsMsg = 0x07 ReceiptsMsg = 0x07
GetProofsMsg = 0x08 GetProofsV1Msg = 0x08
ProofsMsg = 0x09 ProofsV1Msg = 0x09
GetCodeMsg = 0x0a GetCodeMsg = 0x0a
CodeMsg = 0x0b CodeMsg = 0x0b
SendTxMsg = 0x0c SendTxMsg = 0x0c
GetHeaderProofsMsg = 0x0d GetHeaderProofsMsg = 0x0d
HeaderProofsMsg = 0x0e HeaderProofsMsg = 0x0e
// Protocol messages belonging to LPV2
GetProofsV2Msg = 0x0f
ProofsV2Msg = 0x10
GetPPTProofsMsg = 0x11
PPTProofsMsg = 0x12
) )
type errCode int type errCode int

View file

@ -25,7 +25,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
@ -80,23 +79,12 @@ type TrieRequest struct {
OdrRequest OdrRequest
Id *TrieID Id *TrieID
Key []byte Key []byte
Proof []rlp.RawValue Proof *NodeSet
} }
// StoreResult stores the retrieved data in local database // StoreResult stores the retrieved data in local database
func (req *TrieRequest) StoreResult(db ethdb.Database) { func (req *TrieRequest) StoreResult(db ethdb.Database) {
storeProof(db, req.Proof) req.Proof.Store(db)
}
// storeProof stores the new trie nodes obtained from a merkle proof in the database
func storeProof(db ethdb.Database, proof []rlp.RawValue) {
for _, buf := range proof {
hash := crypto.Keccak256(buf)
val, _ := db.Get(hash)
if val == nil {
db.Put(hash, buf)
}
}
} }
// CodeRequest is the ODR request type for retrieving contract code // CodeRequest is the ODR request type for retrieving contract code

View file

@ -79,7 +79,7 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error {
t, _ := trie.New(req.Id.Root, odr.sdb) t, _ := trie.New(req.Id.Root, odr.sdb)
nodes := NewNodeSet() nodes := NewNodeSet()
t.Prove(req.Key, 0, nodes) t.Prove(req.Key, 0, nodes)
req.Proof = nodes.NodeList() req.Proof = nodes
case *CodeRequest: case *CodeRequest:
req.Data, _ = odr.sdb.Get(req.Hash[:]) req.Data, _ = odr.sdb.Get(req.Hash[:])
} }