eth: request id dispatcher and direct req/reply APIs (#23576)
* eth: request ID based message dispatcher * eth: fix dispatcher cancellation, rework fetchers idleness tracker * eth/downloader: drop peers who refuse to serve advertised chains
This commit is contained in:
@ -26,6 +26,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/prque"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
@ -74,10 +75,10 @@ type HeaderRetrievalFn func(common.Hash) *types.Header
|
||||
type blockRetrievalFn func(common.Hash) *types.Block
|
||||
|
||||
// headerRequesterFn is a callback type for sending a header retrieval request.
|
||||
type headerRequesterFn func(common.Hash) error
|
||||
type headerRequesterFn func(common.Hash, chan *eth.Response) (*eth.Request, error)
|
||||
|
||||
// bodyRequesterFn is a callback type for sending a body retrieval request.
|
||||
type bodyRequesterFn func([]common.Hash) error
|
||||
type bodyRequesterFn func([]common.Hash, chan *eth.Response) (*eth.Request, error)
|
||||
|
||||
// headerVerifierFn is a callback type to verify a block's header for fast propagation.
|
||||
type headerVerifierFn func(header *types.Header) error
|
||||
@ -461,15 +462,28 @@ func (f *BlockFetcher) loop() {
|
||||
|
||||
// Create a closure of the fetch and schedule in on a new thread
|
||||
fetchHeader, hashes := f.fetching[hashes[0]].fetchHeader, hashes
|
||||
go func() {
|
||||
go func(peer string) {
|
||||
if f.fetchingHook != nil {
|
||||
f.fetchingHook(hashes)
|
||||
}
|
||||
for _, hash := range hashes {
|
||||
headerFetchMeter.Mark(1)
|
||||
fetchHeader(hash) // Suboptimal, but protocol doesn't allow batch header retrievals
|
||||
go func(hash common.Hash) {
|
||||
resCh := make(chan *eth.Response)
|
||||
|
||||
req, err := fetchHeader(hash, resCh)
|
||||
if err != nil {
|
||||
return // Legacy code, yolo
|
||||
}
|
||||
defer req.Close()
|
||||
|
||||
res := <-resCh
|
||||
res.Done <- nil
|
||||
|
||||
f.FilterHeaders(peer, *res.Res.(*eth.BlockHeadersPacket), time.Now().Add(res.Time))
|
||||
}(hash)
|
||||
}
|
||||
}()
|
||||
}(peer)
|
||||
}
|
||||
// Schedule the next fetch if blocks are still pending
|
||||
f.rescheduleFetch(fetchTimer)
|
||||
@ -497,8 +511,24 @@ func (f *BlockFetcher) loop() {
|
||||
if f.completingHook != nil {
|
||||
f.completingHook(hashes)
|
||||
}
|
||||
fetchBodies := f.completing[hashes[0]].fetchBodies
|
||||
bodyFetchMeter.Mark(int64(len(hashes)))
|
||||
go f.completing[hashes[0]].fetchBodies(hashes)
|
||||
|
||||
go func(peer string, hashes []common.Hash) {
|
||||
resCh := make(chan *eth.Response)
|
||||
|
||||
req, err := fetchBodies(hashes, resCh)
|
||||
if err != nil {
|
||||
return // Legacy code, yolo
|
||||
}
|
||||
defer req.Close()
|
||||
|
||||
res := <-resCh
|
||||
res.Done <- nil
|
||||
|
||||
txs, uncles := res.Res.(*eth.BlockBodiesPacket).Unpack()
|
||||
f.FilterBodies(peer, txs, uncles, time.Now())
|
||||
}(peer, hashes)
|
||||
}
|
||||
// Schedule the next fetch if blocks are still pending
|
||||
f.rescheduleComplete(completeTimer)
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/protocols/eth"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
@ -60,8 +61,8 @@ func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common
|
||||
block.AddTx(tx)
|
||||
}
|
||||
// If the block number is a multiple of 5, add a bonus uncle to the block
|
||||
if i%5 == 0 {
|
||||
block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 1).Hash(), Number: big.NewInt(int64(i - 1))})
|
||||
if i > 0 && i%5 == 0 {
|
||||
block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 2).Hash(), Number: big.NewInt(int64(i - 1))})
|
||||
}
|
||||
})
|
||||
hashes := make([]common.Hash, n+1)
|
||||
@ -195,16 +196,26 @@ func (f *fetcherTester) makeHeaderFetcher(peer string, blocks map[common.Hash]*t
|
||||
closure[hash] = block
|
||||
}
|
||||
// Create a function that return a header from the closure
|
||||
return func(hash common.Hash) error {
|
||||
return func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
||||
// Gather the blocks to return
|
||||
headers := make([]*types.Header, 0, 1)
|
||||
if block, ok := closure[hash]; ok {
|
||||
headers = append(headers, block.Header())
|
||||
}
|
||||
// Return on a new thread
|
||||
go f.fetcher.FilterHeaders(peer, headers, time.Now().Add(drift))
|
||||
|
||||
return nil
|
||||
req := ð.Request{
|
||||
Peer: peer,
|
||||
}
|
||||
res := ð.Response{
|
||||
Req: req,
|
||||
Res: (*eth.BlockHeadersPacket)(&headers),
|
||||
Time: drift,
|
||||
Done: make(chan error, 1), // Ignore the returned status
|
||||
}
|
||||
go func() {
|
||||
sink <- res
|
||||
}()
|
||||
return req, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +226,7 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ
|
||||
closure[hash] = block
|
||||
}
|
||||
// Create a function that returns blocks from the closure
|
||||
return func(hashes []common.Hash) error {
|
||||
return func(hashes []common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
||||
// Gather the block bodies to return
|
||||
transactions := make([][]*types.Transaction, 0, len(hashes))
|
||||
uncles := make([][]*types.Header, 0, len(hashes))
|
||||
@ -227,14 +238,33 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ
|
||||
}
|
||||
}
|
||||
// Return on a new thread
|
||||
go f.fetcher.FilterBodies(peer, transactions, uncles, time.Now().Add(drift))
|
||||
|
||||
return nil
|
||||
bodies := make([]*eth.BlockBody, len(transactions))
|
||||
for i, txs := range transactions {
|
||||
bodies[i] = ð.BlockBody{
|
||||
Transactions: txs,
|
||||
Uncles: uncles[i],
|
||||
}
|
||||
}
|
||||
req := ð.Request{
|
||||
Peer: peer,
|
||||
}
|
||||
res := ð.Response{
|
||||
Req: req,
|
||||
Res: (*eth.BlockBodiesPacket)(&bodies),
|
||||
Time: drift,
|
||||
Done: make(chan error, 1), // Ignore the returned status
|
||||
}
|
||||
go func() {
|
||||
sink <- res
|
||||
}()
|
||||
return req, nil
|
||||
}
|
||||
}
|
||||
|
||||
// verifyFetchingEvent verifies that one single event arrive on a fetching channel.
|
||||
func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool) {
|
||||
t.Helper()
|
||||
|
||||
if arrive {
|
||||
select {
|
||||
case <-fetching:
|
||||
@ -252,6 +282,8 @@ func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool)
|
||||
|
||||
// verifyCompletingEvent verifies that one single event arrive on an completing channel.
|
||||
func verifyCompletingEvent(t *testing.T, completing chan []common.Hash, arrive bool) {
|
||||
t.Helper()
|
||||
|
||||
if arrive {
|
||||
select {
|
||||
case <-completing:
|
||||
@ -269,6 +301,8 @@ func verifyCompletingEvent(t *testing.T, completing chan []common.Hash, arrive b
|
||||
|
||||
// verifyImportEvent verifies that one single event arrive on an import channel.
|
||||
func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) {
|
||||
t.Helper()
|
||||
|
||||
if arrive {
|
||||
select {
|
||||
case <-imported:
|
||||
@ -287,6 +321,8 @@ func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) {
|
||||
// verifyImportCount verifies that exactly count number of events arrive on an
|
||||
// import hook channel.
|
||||
func verifyImportCount(t *testing.T, imported chan interface{}, count int) {
|
||||
t.Helper()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
select {
|
||||
case <-imported:
|
||||
@ -299,6 +335,8 @@ func verifyImportCount(t *testing.T, imported chan interface{}, count int) {
|
||||
|
||||
// verifyImportDone verifies that no more events are arriving on an import channel.
|
||||
func verifyImportDone(t *testing.T, imported chan interface{}) {
|
||||
t.Helper()
|
||||
|
||||
select {
|
||||
case <-imported:
|
||||
t.Fatalf("extra block imported")
|
||||
@ -308,6 +346,8 @@ func verifyImportDone(t *testing.T, imported chan interface{}) {
|
||||
|
||||
// verifyChainHeight verifies the chain height is as expected.
|
||||
func verifyChainHeight(t *testing.T, fetcher *fetcherTester, height uint64) {
|
||||
t.Helper()
|
||||
|
||||
if fetcher.chainHeight() != height {
|
||||
t.Fatalf("chain height mismatch, got %d, want %d", fetcher.chainHeight(), height)
|
||||
}
|
||||
@ -368,13 +408,13 @@ func testConcurrentAnnouncements(t *testing.T, light bool) {
|
||||
secondBodyFetcher := tester.makeBodyFetcher("second", blocks, 0)
|
||||
|
||||
counter := uint32(0)
|
||||
firstHeaderWrapper := func(hash common.Hash) error {
|
||||
firstHeaderWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
||||
atomic.AddUint32(&counter, 1)
|
||||
return firstHeaderFetcher(hash)
|
||||
return firstHeaderFetcher(hash, sink)
|
||||
}
|
||||
secondHeaderWrapper := func(hash common.Hash) error {
|
||||
secondHeaderWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
||||
atomic.AddUint32(&counter, 1)
|
||||
return secondHeaderFetcher(hash)
|
||||
return secondHeaderFetcher(hash, sink)
|
||||
}
|
||||
// Iteratively announce blocks until all are imported
|
||||
imported := make(chan interface{})
|
||||
@ -468,15 +508,20 @@ func testPendingDeduplication(t *testing.T, light bool) {
|
||||
|
||||
delay := 50 * time.Millisecond
|
||||
counter := uint32(0)
|
||||
headerWrapper := func(hash common.Hash) error {
|
||||
headerWrapper := func(hash common.Hash, sink chan *eth.Response) (*eth.Request, error) {
|
||||
atomic.AddUint32(&counter, 1)
|
||||
|
||||
// Simulate a long running fetch
|
||||
go func() {
|
||||
time.Sleep(delay)
|
||||
headerFetcher(hash)
|
||||
}()
|
||||
return nil
|
||||
resink := make(chan *eth.Response)
|
||||
req, err := headerFetcher(hash, resink)
|
||||
if err == nil {
|
||||
go func() {
|
||||
res := <-resink
|
||||
time.Sleep(delay)
|
||||
sink <- res
|
||||
}()
|
||||
}
|
||||
return req, err
|
||||
}
|
||||
checkNonExist := func() bool {
|
||||
return tester.getBlock(hashes[0]) == nil
|
||||
|
Reference in New Issue
Block a user