Skip to content

Commit 9f6bb49

Browse files
authored
les, light: remove untrusted header retrieval in ODR (#21907)
* les, light: remove untrusted header retrieval in ODR * les: polish * light: check the hash equality in odr
1 parent 817a3fb commit 9f6bb49

File tree

9 files changed

+125
-82
lines changed

9 files changed

+125
-82
lines changed

les/client_handler.go

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package les
1818

1919
import (
20+
"context"
2021
"math/big"
2122
"sync"
2223
"sync/atomic"
@@ -200,14 +201,23 @@ func (h *clientHandler) handleMsg(p *serverPeer) error {
200201
p.fcServer.ReceivedReply(resp.ReqID, resp.BV)
201202
p.answeredRequest(resp.ReqID)
202203

203-
// Filter out any explicitly requested headers, deliver the rest to the downloader
204-
filter := len(headers) == 1
205-
if filter {
206-
headers = h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers)
207-
}
208-
if len(headers) != 0 || !filter {
209-
if err := h.downloader.DeliverHeaders(p.id, headers); err != nil {
210-
log.Debug("Failed to deliver headers", "err", err)
204+
// Filter out the explicitly requested header by the retriever
205+
if h.backend.retriever.requested(resp.ReqID) {
206+
deliverMsg = &Msg{
207+
MsgType: MsgBlockHeaders,
208+
ReqID: resp.ReqID,
209+
Obj: resp.Headers,
210+
}
211+
} else {
212+
// Filter out any explicitly requested headers, deliver the rest to the downloader
213+
filter := len(headers) == 1
214+
if filter {
215+
headers = h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers)
216+
}
217+
if len(headers) != 0 || !filter {
218+
if err := h.downloader.DeliverHeaders(p.id, headers); err != nil {
219+
log.Debug("Failed to deliver headers", "err", err)
220+
}
211221
}
212222
}
213223
case BlockBodiesMsg:
@@ -394,6 +404,42 @@ func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip
394404
return nil
395405
}
396406

407+
// RetrieveSingleHeaderByNumber requests a single header by the specified block
408+
// number. This function will wait the response until it's timeout or delivered.
409+
func (pc *peerConnection) RetrieveSingleHeaderByNumber(context context.Context, number uint64) (*types.Header, error) {
410+
reqID := genReqID()
411+
rq := &distReq{
412+
getCost: func(dp distPeer) uint64 {
413+
peer := dp.(*serverPeer)
414+
return peer.getRequestCost(GetBlockHeadersMsg, 1)
415+
},
416+
canSend: func(dp distPeer) bool {
417+
return dp.(*serverPeer) == pc.peer
418+
},
419+
request: func(dp distPeer) func() {
420+
peer := dp.(*serverPeer)
421+
cost := peer.getRequestCost(GetBlockHeadersMsg, 1)
422+
peer.fcServer.QueuedRequest(reqID, cost)
423+
return func() { peer.requestHeadersByNumber(reqID, number, 1, 0, false) }
424+
},
425+
}
426+
var header *types.Header
427+
if err := pc.handler.backend.retriever.retrieve(context, reqID, rq, func(peer distPeer, msg *Msg) error {
428+
if msg.MsgType != MsgBlockHeaders {
429+
return errInvalidMessageType
430+
}
431+
headers := msg.Obj.([]*types.Header)
432+
if len(headers) != 1 {
433+
return errInvalidEntryCount
434+
}
435+
header = headers[0]
436+
return nil
437+
}, nil); err != nil {
438+
return nil, err
439+
}
440+
return header, nil
441+
}
442+
397443
// downloaderPeerNotify implements peerSetNotify
398444
type downloaderPeerNotify clientHandler
399445

les/odr.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"github.com/ethereum/go-ethereum/core"
2525
"github.com/ethereum/go-ethereum/ethdb"
2626
"github.com/ethereum/go-ethereum/light"
27-
"github.com/ethereum/go-ethereum/log"
2827
)
2928

3029
// LesOdr implements light.OdrBackend
@@ -83,7 +82,8 @@ func (odr *LesOdr) IndexerConfig() *light.IndexerConfig {
8382
}
8483

8584
const (
86-
MsgBlockBodies = iota
85+
MsgBlockHeaders = iota
86+
MsgBlockBodies
8787
MsgCode
8888
MsgReceipts
8989
MsgProofsV2
@@ -122,13 +122,17 @@ func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err erro
122122
return func() { lreq.Request(reqID, p) }
123123
},
124124
}
125-
sent := mclock.Now()
126-
if err = odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err == nil {
127-
// retrieved from network, store in db
128-
req.StoreResult(odr.db)
125+
126+
defer func(sent mclock.AbsTime) {
127+
if err != nil {
128+
return
129+
}
129130
requestRTT.Update(time.Duration(mclock.Now() - sent))
130-
} else {
131-
log.Debug("Failed to retrieve data from network", "err", err)
131+
}(mclock.Now())
132+
133+
if err := odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err != nil {
134+
return err
132135
}
133-
return
136+
req.StoreResult(odr.db)
137+
return nil
134138
}

les/odr_requests.go

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -327,9 +327,6 @@ func (r *ChtRequest) CanSend(peer *serverPeer) bool {
327327
peer.lock.RLock()
328328
defer peer.lock.RUnlock()
329329

330-
if r.Untrusted {
331-
return peer.headInfo.Number >= r.BlockNum && peer.id == r.PeerId
332-
}
333330
return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize
334331
}
335332

@@ -369,39 +366,34 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error {
369366
if err := rlp.DecodeBytes(headerEnc, header); err != nil {
370367
return errHeaderUnavailable
371368
}
372-
373369
// Verify the CHT
374-
// Note: For untrusted CHT request, there is no proof response but
375-
// header data.
376-
var node light.ChtNode
377-
if !r.Untrusted {
378-
var encNumber [8]byte
379-
binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
380-
381-
reads := &readTraceDB{db: nodeSet}
382-
value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads)
383-
if err != nil {
384-
return fmt.Errorf("merkle proof verification failed: %v", err)
385-
}
386-
if len(reads.reads) != nodeSet.KeyCount() {
387-
return errUselessNodes
388-
}
370+
var (
371+
node light.ChtNode
372+
encNumber [8]byte
373+
)
374+
binary.BigEndian.PutUint64(encNumber[:], r.BlockNum)
389375

390-
if err := rlp.DecodeBytes(value, &node); err != nil {
391-
return err
392-
}
393-
if node.Hash != header.Hash() {
394-
return errCHTHashMismatch
395-
}
396-
if r.BlockNum != header.Number.Uint64() {
397-
return errCHTNumberMismatch
398-
}
376+
reads := &readTraceDB{db: nodeSet}
377+
value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads)
378+
if err != nil {
379+
return fmt.Errorf("merkle proof verification failed: %v", err)
380+
}
381+
if len(reads.reads) != nodeSet.KeyCount() {
382+
return errUselessNodes
383+
}
384+
if err := rlp.DecodeBytes(value, &node); err != nil {
385+
return err
386+
}
387+
if node.Hash != header.Hash() {
388+
return errCHTHashMismatch
389+
}
390+
if r.BlockNum != header.Number.Uint64() {
391+
return errCHTNumberMismatch
399392
}
400393
// Verifications passed, store and return
401394
r.Header = header
402395
r.Proof = nodeSet
403-
r.Td = node.Td // For untrusted request, td here is nil, todo improve the les/2 protocol
404-
396+
r.Td = node.Td
405397
return nil
406398
}
407399

les/retrieve.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,15 @@ func (rm *retrieveManager) sendReq(reqID uint64, req *distReq, val validatorFunc
155155
return r
156156
}
157157

158+
// requested reports whether the request with given reqid is sent by the retriever.
159+
func (rm *retrieveManager) requested(reqId uint64) bool {
160+
rm.lock.RLock()
161+
defer rm.lock.RUnlock()
162+
163+
_, ok := rm.sentReqs[reqId]
164+
return ok
165+
}
166+
158167
// deliver is called by the LES protocol manager to deliver reply messages to waiting requests
159168
func (rm *retrieveManager) deliver(peer distPeer, msg *Msg) error {
160169
rm.lock.RLock()

les/sync.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ func (h *clientHandler) validateCheckpoint(peer *serverPeer) error {
5656
defer cancel()
5757

5858
// Fetch the block header corresponding to the checkpoint registration.
59-
cp := peer.checkpoint
60-
header, err := light.GetUntrustedHeaderByNumber(ctx, h.backend.odr, peer.checkpointNumber, peer.id)
59+
wrapPeer := &peerConnection{handler: h, peer: peer}
60+
header, err := wrapPeer.RetrieveSingleHeaderByNumber(ctx, peer.checkpointNumber)
6161
if err != nil {
6262
return err
6363
}
@@ -66,7 +66,7 @@ func (h *clientHandler) validateCheckpoint(peer *serverPeer) error {
6666
if err != nil {
6767
return err
6868
}
69-
events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, cp.SectionIndex, cp.Hash())
69+
events := h.backend.oracle.Contract().LookupCheckpointEvents(logs, peer.checkpoint.SectionIndex, peer.checkpoint.Hash())
7070
if len(events) == 0 {
7171
return errInvalidCheckpoint
7272
}

les/sync_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func testCheckpointSyncing(t *testing.T, protocol int, syncMode int) {
5353
time.Sleep(10 * time.Millisecond)
5454
}
5555
}
56-
// Generate 512+4 blocks (totally 1 CHT sections)
56+
// Generate 128+1 blocks (totally 1 CHT sections)
5757
server, client, tearDown := newClientServerEnv(t, int(config.ChtSize+config.ChtConfirms), protocol, waitIndexers, nil, 0, false, false, true)
5858
defer tearDown()
5959

light/odr.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,6 @@ func (req *ReceiptsRequest) StoreResult(db ethdb.Database) {
135135

136136
// ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie
137137
type ChtRequest struct {
138-
Untrusted bool // Indicator whether the result retrieved is trusted or not
139-
PeerId string // The specified peer id from which to retrieve data.
140138
Config *IndexerConfig
141139
ChtNum, BlockNum uint64
142140
ChtRoot common.Hash
@@ -148,12 +146,9 @@ type ChtRequest struct {
148146
// StoreResult stores the retrieved data in local database
149147
func (req *ChtRequest) StoreResult(db ethdb.Database) {
150148
hash, num := req.Header.Hash(), req.Header.Number.Uint64()
151-
152-
if !req.Untrusted {
153-
rawdb.WriteHeader(db, req.Header)
154-
rawdb.WriteTd(db, hash, num, req.Td)
155-
rawdb.WriteCanonicalHash(db, hash, num)
156-
}
149+
rawdb.WriteHeader(db, req.Header)
150+
rawdb.WriteTd(db, hash, num, req.Td)
151+
rawdb.WriteCanonicalHash(db, hash, num)
157152
}
158153

159154
// BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure

light/odr_util.go

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,23 @@ package light
1919
import (
2020
"bytes"
2121
"context"
22+
"errors"
2223
"math/big"
2324

2425
"github.com/ethereum/go-ethereum/common"
2526
"github.com/ethereum/go-ethereum/core"
2627
"github.com/ethereum/go-ethereum/core/rawdb"
2728
"github.com/ethereum/go-ethereum/core/types"
28-
"github.com/ethereum/go-ethereum/crypto"
2929
"github.com/ethereum/go-ethereum/rlp"
3030
)
3131

32-
var sha3Nil = crypto.Keccak256Hash(nil)
32+
// errNonCanonicalHash is returned if the requested chain data doesn't belong
33+
// to the canonical chain. ODR can only retrieve the canonical chain data covered
34+
// by the CHT or Bloom trie for verification.
35+
var errNonCanonicalHash = errors.New("hash is not currently canonical")
3336

3437
// GetHeaderByNumber retrieves the canonical block header corresponding to the
35-
// given number.
38+
// given number. The returned header is proven by local CHT.
3639
func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) {
3740
// Try to find it in the local database first.
3841
db := odr.Database()
@@ -63,25 +66,6 @@ func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*typ
6366
return r.Header, nil
6467
}
6568

66-
// GetUntrustedHeaderByNumber retrieves specified block header without
67-
// correctness checking. Note this function should only be used in light
68-
// client checkpoint syncing.
69-
func GetUntrustedHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64, peerId string) (*types.Header, error) {
70-
// todo(rjl493456442) it's a hack to retrieve headers which is not covered
71-
// by CHT. Fix it in LES4
72-
r := &ChtRequest{
73-
BlockNum: number,
74-
ChtNum: number / odr.IndexerConfig().ChtSize,
75-
Untrusted: true,
76-
PeerId: peerId,
77-
Config: odr.IndexerConfig(),
78-
}
79-
if err := odr.Retrieve(ctx, r); err != nil {
80-
return nil, err
81-
}
82-
return r.Header, nil
83-
}
84-
8569
// GetCanonicalHash retrieves the canonical block hash corresponding to the number.
8670
func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) {
8771
hash := rawdb.ReadCanonicalHash(odr.Database(), number)
@@ -102,10 +86,13 @@ func GetTd(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64)
10286
if td != nil {
10387
return td, nil
10488
}
105-
_, err := GetHeaderByNumber(ctx, odr, number)
89+
header, err := GetHeaderByNumber(ctx, odr, number)
10690
if err != nil {
10791
return nil, err
10892
}
93+
if header.Hash() != hash {
94+
return nil, errNonCanonicalHash
95+
}
10996
// <hash, number> -> td mapping already be stored in db, get it.
11097
return rawdb.ReadTd(odr.Database(), hash, number), nil
11198
}
@@ -120,6 +107,9 @@ func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number ui
120107
if err != nil {
121108
return nil, errNoHeader
122109
}
110+
if header.Hash() != hash {
111+
return nil, errNonCanonicalHash
112+
}
123113
r := &BlockRequest{Hash: hash, Number: number, Header: header}
124114
if err := odr.Retrieve(ctx, r); err != nil {
125115
return nil, err
@@ -167,6 +157,9 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num
167157
if err != nil {
168158
return nil, errNoHeader
169159
}
160+
if header.Hash() != hash {
161+
return nil, errNonCanonicalHash
162+
}
170163
r := &ReceiptsRequest{Hash: hash, Number: number, Header: header}
171164
if err := odr.Retrieve(ctx, r); err != nil {
172165
return nil, err

light/trie.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ import (
3030
"github.com/ethereum/go-ethereum/trie"
3131
)
3232

33+
var (
34+
sha3Nil = crypto.Keccak256Hash(nil)
35+
)
36+
3337
func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB {
3438
state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr), nil)
3539
return state

0 commit comments

Comments
 (0)