mirror of https://github.com/ethereum/go-ethereum
parent
dcffb7777f
commit
049e17116e
@ -0,0 +1,319 @@ |
||||
// Copyright 2020 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 fetcher |
||||
|
||||
import ( |
||||
"math/rand" |
||||
"time" |
||||
|
||||
mapset "github.com/deckarep/golang-set" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/log" |
||||
) |
||||
|
||||
var ( |
||||
// txAnnounceLimit is the maximum number of unique transaction a peer
|
||||
// can announce in a short time.
|
||||
txAnnounceLimit = 4096 |
||||
|
||||
// txFetchTimeout is the maximum allotted time to return an explicitly
|
||||
// requested transaction.
|
||||
txFetchTimeout = 5 * time.Second |
||||
|
||||
// MaxTransactionFetch is the maximum transaction number can be fetched
|
||||
// in one request. The rationale to pick this value is:
|
||||
// In eth protocol, the softResponseLimit is 2MB. Nowdays according to
|
||||
// Etherscan the average transaction size is around 200B, so in theory
|
||||
// we can include lots of transaction in a single protocol packet. However
|
||||
// the maximum size of a single transaction is raised to 128KB, so pick
|
||||
// a middle value here to ensure we can maximize the efficiency of the
|
||||
// retrieval and response size overflow won't happen in most cases.
|
||||
MaxTransactionFetch = 256 |
||||
|
||||
// underpriceSetSize is the size of underprice set which used for maintaining
|
||||
// the set of underprice transactions.
|
||||
underpriceSetSize = 4096 |
||||
) |
||||
|
||||
// txAnnounce is the notification of the availability of a single
|
||||
// new transaction in the network.
|
||||
type txAnnounce struct { |
||||
origin string // Identifier of the peer originating the notification
|
||||
time time.Time // Timestamp of the announcement
|
||||
fetchTxs func([]common.Hash) // Callback for retrieving transaction from specified peer
|
||||
} |
||||
|
||||
// txsAnnounce is the notification of the availability of a batch
|
||||
// of new transactions in the network.
|
||||
type txsAnnounce struct { |
||||
hashes []common.Hash // Batch of transaction hashes being announced
|
||||
origin string // Identifier of the peer originating the notification
|
||||
time time.Time // Timestamp of the announcement
|
||||
fetchTxs func([]common.Hash) // Callback for retrieving transaction from specified peer
|
||||
} |
||||
|
||||
// TxFetcher is responsible for retrieving new transaction based
|
||||
// on the announcement.
|
||||
type TxFetcher struct { |
||||
notify chan *txsAnnounce |
||||
cleanup chan []common.Hash |
||||
quit chan struct{} |
||||
|
||||
// Announce states
|
||||
announces map[string]int // Per peer transaction announce counts to prevent memory exhaustion
|
||||
announced map[common.Hash][]*txAnnounce // Announced transactions, scheduled for fetching
|
||||
fetching map[common.Hash]*txAnnounce // Announced transactions, currently fetching
|
||||
underpriced mapset.Set // Transaction set whose price is too low for accepting
|
||||
|
||||
// Callbacks
|
||||
hasTx func(common.Hash) bool // Retrieves a tx from the local txpool
|
||||
addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool
|
||||
dropPeer func(string) // Drop the specified peer
|
||||
|
||||
// Hooks
|
||||
announceHook func([]common.Hash) // Hook which is called when a batch transactions are announced
|
||||
importTxsHook func([]*types.Transaction) // Hook which is called when a batch of transactions are imported.
|
||||
dropHook func(string) // Hook which is called when a peer is dropped
|
||||
cleanupHook func([]common.Hash) // Hook which is called when internal status is cleaned
|
||||
rejectUnderprice func(common.Hash) // Hook which is called when underprice transaction is rejected
|
||||
} |
||||
|
||||
// NewTxFetcher creates a transaction fetcher to retrieve transaction
|
||||
// based on hash announcements.
|
||||
func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, dropPeer func(string)) *TxFetcher { |
||||
return &TxFetcher{ |
||||
notify: make(chan *txsAnnounce), |
||||
cleanup: make(chan []common.Hash), |
||||
quit: make(chan struct{}), |
||||
announces: make(map[string]int), |
||||
announced: make(map[common.Hash][]*txAnnounce), |
||||
fetching: make(map[common.Hash]*txAnnounce), |
||||
underpriced: mapset.NewSet(), |
||||
hasTx: hasTx, |
||||
addTxs: addTxs, |
||||
dropPeer: dropPeer, |
||||
} |
||||
} |
||||
|
||||
// Notify announces the fetcher of the potential availability of a
|
||||
// new transaction in the network.
|
||||
func (f *TxFetcher) Notify(peer string, hashes []common.Hash, time time.Time, fetchTxs func([]common.Hash)) error { |
||||
announce := &txsAnnounce{ |
||||
hashes: hashes, |
||||
time: time, |
||||
origin: peer, |
||||
fetchTxs: fetchTxs, |
||||
} |
||||
select { |
||||
case f.notify <- announce: |
||||
return nil |
||||
case <-f.quit: |
||||
return errTerminated |
||||
} |
||||
} |
||||
|
||||
// EnqueueTxs imports a batch of received transaction into fetcher.
|
||||
func (f *TxFetcher) EnqueueTxs(peer string, txs []*types.Transaction) error { |
||||
var ( |
||||
drop bool |
||||
hashes []common.Hash |
||||
) |
||||
errs := f.addTxs(txs) |
||||
for i, err := range errs { |
||||
if err != nil { |
||||
// Drop peer if the received transaction isn't signed properly.
|
||||
drop = (drop || err == core.ErrInvalidSender) |
||||
txFetchInvalidMeter.Mark(1) |
||||
|
||||
// Track the transaction hash if the price is too low for us.
|
||||
// Avoid re-request this transaction when we receive another
|
||||
// announcement.
|
||||
if err == core.ErrUnderpriced { |
||||
for f.underpriced.Cardinality() >= underpriceSetSize { |
||||
f.underpriced.Pop() |
||||
} |
||||
f.underpriced.Add(txs[i].Hash()) |
||||
} |
||||
} |
||||
hashes = append(hashes, txs[i].Hash()) |
||||
} |
||||
if f.importTxsHook != nil { |
||||
f.importTxsHook(txs) |
||||
} |
||||
// Drop the peer if some transaction failed signature verification.
|
||||
// We can regard this peer is trying to DOS us by feeding lots of
|
||||
// random hashes.
|
||||
if drop { |
||||
f.dropPeer(peer) |
||||
if f.dropHook != nil { |
||||
f.dropHook(peer) |
||||
} |
||||
} |
||||
select { |
||||
case f.cleanup <- hashes: |
||||
return nil |
||||
case <-f.quit: |
||||
return errTerminated |
||||
} |
||||
} |
||||
|
||||
// Start boots up the announcement based synchroniser, accepting and processing
|
||||
// hash notifications and block fetches until termination requested.
|
||||
func (f *TxFetcher) Start() { |
||||
go f.loop() |
||||
} |
||||
|
||||
// Stop terminates the announcement based synchroniser, canceling all pending
|
||||
// operations.
|
||||
func (f *TxFetcher) Stop() { |
||||
close(f.quit) |
||||
} |
||||
|
||||
func (f *TxFetcher) loop() { |
||||
fetchTimer := time.NewTimer(0) |
||||
|
||||
for { |
||||
// Clean up any expired transaction fetches.
|
||||
// There are many cases can lead to it:
|
||||
// * We send the request to busy peer which can reply immediately
|
||||
// * We send the request to malicious peer which doesn't reply deliberately
|
||||
// * We send the request to normal peer for a batch of transaction, but some
|
||||
// transactions have been included into blocks. According to EIP these txs
|
||||
// won't be included.
|
||||
// But it's fine to delete the fetching record and reschedule fetching iff we
|
||||
// receive the annoucement again.
|
||||
for hash, announce := range f.fetching { |
||||
if time.Since(announce.time) > txFetchTimeout { |
||||
delete(f.fetching, hash) |
||||
txFetchTimeoutMeter.Mark(1) |
||||
} |
||||
} |
||||
select { |
||||
case anno := <-f.notify: |
||||
txAnnounceInMeter.Mark(int64(len(anno.hashes))) |
||||
|
||||
// Drop the new announce if there are too many accumulated.
|
||||
count := f.announces[anno.origin] + len(anno.hashes) |
||||
if count > txAnnounceLimit { |
||||
txAnnounceDOSMeter.Mark(int64(count - txAnnounceLimit)) |
||||
break |
||||
} |
||||
f.announces[anno.origin] = count |
||||
|
||||
// All is well, schedule the announce if transaction is not yet downloading
|
||||
empty := len(f.announced) == 0 |
||||
for _, hash := range anno.hashes { |
||||
if _, ok := f.fetching[hash]; ok { |
||||
continue |
||||
} |
||||
if f.underpriced.Contains(hash) { |
||||
txAnnounceUnderpriceMeter.Mark(1) |
||||
if f.rejectUnderprice != nil { |
||||
f.rejectUnderprice(hash) |
||||
} |
||||
continue |
||||
} |
||||
f.announced[hash] = append(f.announced[hash], &txAnnounce{ |
||||
origin: anno.origin, |
||||
time: anno.time, |
||||
fetchTxs: anno.fetchTxs, |
||||
}) |
||||
} |
||||
if empty && len(f.announced) > 0 { |
||||
f.reschedule(fetchTimer) |
||||
} |
||||
if f.announceHook != nil { |
||||
f.announceHook(anno.hashes) |
||||
} |
||||
case <-fetchTimer.C: |
||||
// At least one tx's timer ran out, check for needing retrieval
|
||||
request := make(map[string][]common.Hash) |
||||
|
||||
for hash, announces := range f.announced { |
||||
if time.Since(announces[0].time) > arriveTimeout-gatherSlack { |
||||
// Pick a random peer to retrieve from, reset all others
|
||||
announce := announces[rand.Intn(len(announces))] |
||||
f.forgetHash(hash) |
||||
|
||||
// Skip fetching if we already receive the transaction.
|
||||
if f.hasTx(hash) { |
||||
txAnnounceSkipMeter.Mark(1) |
||||
continue |
||||
} |
||||
// If the transaction still didn't arrive, queue for fetching
|
||||
request[announce.origin] = append(request[announce.origin], hash) |
||||
f.fetching[hash] = announce |
||||
} |
||||
} |
||||
// Send out all block header requests
|
||||
for peer, hashes := range request { |
||||
log.Trace("Fetching scheduled transactions", "peer", peer, "txs", hashes) |
||||
fetchTxs := f.fetching[hashes[0]].fetchTxs |
||||
fetchTxs(hashes) |
||||
txFetchOutMeter.Mark(int64(len(hashes))) |
||||
} |
||||
// Schedule the next fetch if blocks are still pending
|
||||
f.reschedule(fetchTimer) |
||||
case hashes := <-f.cleanup: |
||||
for _, hash := range hashes { |
||||
f.forgetHash(hash) |
||||
anno, exist := f.fetching[hash] |
||||
if !exist { |
||||
txBroadcastInMeter.Mark(1) // Directly transaction propagation
|
||||
continue |
||||
} |
||||
txFetchDurationTimer.UpdateSince(anno.time) |
||||
txFetchSuccessMeter.Mark(1) |
||||
delete(f.fetching, hash) |
||||
} |
||||
if f.cleanupHook != nil { |
||||
f.cleanupHook(hashes) |
||||
} |
||||
case <-f.quit: |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
// rescheduleFetch resets the specified fetch timer to the next blockAnnounce timeout.
|
||||
func (f *TxFetcher) reschedule(fetch *time.Timer) { |
||||
// Short circuit if no transactions are announced
|
||||
if len(f.announced) == 0 { |
||||
return |
||||
} |
||||
// Otherwise find the earliest expiring announcement
|
||||
earliest := time.Now() |
||||
for _, announces := range f.announced { |
||||
if earliest.After(announces[0].time) { |
||||
earliest = announces[0].time |
||||
} |
||||
} |
||||
fetch.Reset(arriveTimeout - time.Since(earliest)) |
||||
} |
||||
|
||||
func (f *TxFetcher) forgetHash(hash common.Hash) { |
||||
// Remove all pending announces and decrement DOS counters
|
||||
for _, announce := range f.announced[hash] { |
||||
f.announces[announce.origin]-- |
||||
if f.announces[announce.origin] <= 0 { |
||||
delete(f.announces, announce.origin) |
||||
} |
||||
} |
||||
delete(f.announced, hash) |
||||
} |
@ -0,0 +1,318 @@ |
||||
// Copyright 2020 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 fetcher |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
"math/big" |
||||
"math/rand" |
||||
"sync" |
||||
"sync/atomic" |
||||
"testing" |
||||
"time" |
||||
|
||||
"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" |
||||
) |
||||
|
||||
func init() { |
||||
rand.Seed(int64(time.Now().Nanosecond())) |
||||
|
||||
txAnnounceLimit = 64 |
||||
MaxTransactionFetch = 16 |
||||
} |
||||
|
||||
func makeTransactions(key *ecdsa.PrivateKey, target int) []*types.Transaction { |
||||
var txs []*types.Transaction |
||||
|
||||
for i := 0; i < target; i++ { |
||||
random := rand.Uint32() |
||||
tx := types.NewTransaction(uint64(random), common.Address{0x1, 0x2, 0x3}, big.NewInt(int64(random)), 100, big.NewInt(int64(random)), nil) |
||||
tx, _ = types.SignTx(tx, types.NewEIP155Signer(big.NewInt(1)), key) |
||||
txs = append(txs, tx) |
||||
} |
||||
return txs |
||||
} |
||||
|
||||
func makeUnsignedTransactions(key *ecdsa.PrivateKey, target int) []*types.Transaction { |
||||
var txs []*types.Transaction |
||||
|
||||
for i := 0; i < target; i++ { |
||||
random := rand.Uint32() |
||||
tx := types.NewTransaction(uint64(random), common.Address{0x1, 0x2, 0x3}, big.NewInt(int64(random)), 100, big.NewInt(int64(random)), nil) |
||||
txs = append(txs, tx) |
||||
} |
||||
return txs |
||||
} |
||||
|
||||
type txfetcherTester struct { |
||||
fetcher *TxFetcher |
||||
|
||||
priceLimit *big.Int |
||||
sender *ecdsa.PrivateKey |
||||
senderAddr common.Address |
||||
signer types.Signer |
||||
txs map[common.Hash]*types.Transaction |
||||
dropped map[string]struct{} |
||||
lock sync.RWMutex |
||||
} |
||||
|
||||
func newTxFetcherTester() *txfetcherTester { |
||||
key, _ := crypto.GenerateKey() |
||||
addr := crypto.PubkeyToAddress(key.PublicKey) |
||||
t := &txfetcherTester{ |
||||
sender: key, |
||||
senderAddr: addr, |
||||
signer: types.NewEIP155Signer(big.NewInt(1)), |
||||
txs: make(map[common.Hash]*types.Transaction), |
||||
dropped: make(map[string]struct{}), |
||||
} |
||||
t.fetcher = NewTxFetcher(t.hasTx, t.addTxs, t.dropPeer) |
||||
t.fetcher.Start() |
||||
return t |
||||
} |
||||
|
||||
func (t *txfetcherTester) hasTx(hash common.Hash) bool { |
||||
t.lock.RLock() |
||||
defer t.lock.RUnlock() |
||||
|
||||
return t.txs[hash] != nil |
||||
} |
||||
|
||||
func (t *txfetcherTester) addTxs(txs []*types.Transaction) []error { |
||||
t.lock.Lock() |
||||
defer t.lock.Unlock() |
||||
|
||||
var errors []error |
||||
for _, tx := range txs { |
||||
// Make sure the transaction is signed properly
|
||||
_, err := types.Sender(t.signer, tx) |
||||
if err != nil { |
||||
errors = append(errors, core.ErrInvalidSender) |
||||
continue |
||||
} |
||||
// Make sure the price is high enough to accpet
|
||||
if t.priceLimit != nil && tx.GasPrice().Cmp(t.priceLimit) < 0 { |
||||
errors = append(errors, core.ErrUnderpriced) |
||||
continue |
||||
} |
||||
t.txs[tx.Hash()] = tx |
||||
errors = append(errors, nil) |
||||
} |
||||
return errors |
||||
} |
||||
|
||||
func (t *txfetcherTester) dropPeer(id string) { |
||||
t.lock.Lock() |
||||
defer t.lock.Unlock() |
||||
|
||||
t.dropped[id] = struct{}{} |
||||
} |
||||
|
||||
// makeTxFetcher retrieves a batch of transaction associated with a simulated peer.
|
||||
func (t *txfetcherTester) makeTxFetcher(peer string, txs []*types.Transaction) func(hashes []common.Hash) { |
||||
closure := make(map[common.Hash]*types.Transaction) |
||||
for _, tx := range txs { |
||||
closure[tx.Hash()] = tx |
||||
} |
||||
return func(hashes []common.Hash) { |
||||
var txs []*types.Transaction |
||||
for _, hash := range hashes { |
||||
tx := closure[hash] |
||||
if tx == nil { |
||||
continue |
||||
} |
||||
txs = append(txs, tx) |
||||
} |
||||
// Return on a new thread
|
||||
go t.fetcher.EnqueueTxs(peer, txs) |
||||
} |
||||
} |
||||
|
||||
func TestSequentialTxAnnouncements(t *testing.T) { |
||||
tester := newTxFetcherTester() |
||||
txs := makeTransactions(tester.sender, txAnnounceLimit) |
||||
|
||||
retrieveTxs := tester.makeTxFetcher("peer", txs) |
||||
|
||||
newTxsCh := make(chan struct{}) |
||||
tester.fetcher.importTxsHook = func(transactions []*types.Transaction) { |
||||
newTxsCh <- struct{}{} |
||||
} |
||||
for _, tx := range txs { |
||||
tester.fetcher.Notify("peer", []common.Hash{tx.Hash()}, time.Now().Add(-arriveTimeout), retrieveTxs) |
||||
select { |
||||
case <-newTxsCh: |
||||
case <-time.NewTimer(time.Second).C: |
||||
t.Fatalf("timeout") |
||||
} |
||||
} |
||||
if len(tester.txs) != len(txs) { |
||||
t.Fatalf("Imported transaction number mismatch, want %d, got %d", len(txs), len(tester.txs)) |
||||
} |
||||
} |
||||
|
||||
func TestConcurrentAnnouncements(t *testing.T) { |
||||
tester := newTxFetcherTester() |
||||
txs := makeTransactions(tester.sender, txAnnounceLimit) |
||||
|
||||
txFetcherFn1 := tester.makeTxFetcher("peer1", txs) |
||||
txFetcherFn2 := tester.makeTxFetcher("peer2", txs) |
||||
|
||||
var ( |
||||
count uint32 |
||||
done = make(chan struct{}) |
||||
) |
||||
tester.fetcher.importTxsHook = func(transactions []*types.Transaction) { |
||||
atomic.AddUint32(&count, uint32(len(transactions))) |
||||
if atomic.LoadUint32(&count) >= uint32(txAnnounceLimit) { |
||||
done <- struct{}{} |
||||
} |
||||
} |
||||
for _, tx := range txs { |
||||
tester.fetcher.Notify("peer1", []common.Hash{tx.Hash()}, time.Now().Add(-arriveTimeout), txFetcherFn1) |
||||
tester.fetcher.Notify("peer2", []common.Hash{tx.Hash()}, time.Now().Add(-arriveTimeout+time.Millisecond), txFetcherFn2) |
||||
tester.fetcher.Notify("peer2", []common.Hash{tx.Hash()}, time.Now().Add(-arriveTimeout-time.Millisecond), txFetcherFn2) |
||||
} |
||||
select { |
||||
case <-done: |
||||
case <-time.NewTimer(time.Second).C: |
||||
t.Fatalf("timeout") |
||||
} |
||||
} |
||||
|
||||
func TestBatchAnnouncements(t *testing.T) { |
||||
tester := newTxFetcherTester() |
||||
txs := makeTransactions(tester.sender, txAnnounceLimit) |
||||
|
||||
retrieveTxs := tester.makeTxFetcher("peer", txs) |
||||
|
||||
var count uint32 |
||||
var done = make(chan struct{}) |
||||
tester.fetcher.importTxsHook = func(txs []*types.Transaction) { |
||||
atomic.AddUint32(&count, uint32(len(txs))) |
||||
|
||||
if atomic.LoadUint32(&count) >= uint32(txAnnounceLimit) { |
||||
done <- struct{}{} |
||||
} |
||||
} |
||||
// Send all announces which exceeds the limit.
|
||||
var hashes []common.Hash |
||||
for _, tx := range txs { |
||||
hashes = append(hashes, tx.Hash()) |
||||
} |
||||
tester.fetcher.Notify("peer", hashes, time.Now(), retrieveTxs) |
||||
|
||||
select { |
||||
case <-done: |
||||
case <-time.NewTimer(time.Second).C: |
||||
t.Fatalf("timeout") |
||||
} |
||||
} |
||||
|
||||
func TestPropagationAfterAnnounce(t *testing.T) { |
||||
tester := newTxFetcherTester() |
||||
txs := makeTransactions(tester.sender, txAnnounceLimit) |
||||
|
||||
var cleaned = make(chan struct{}) |
||||
tester.fetcher.cleanupHook = func(hashes []common.Hash) { |
||||
cleaned <- struct{}{} |
||||
} |
||||
retrieveTxs := tester.makeTxFetcher("peer", txs) |
||||
for _, tx := range txs { |
||||
tester.fetcher.Notify("peer", []common.Hash{tx.Hash()}, time.Now(), retrieveTxs) |
||||
tester.fetcher.EnqueueTxs("peer", []*types.Transaction{tx}) |
||||
|
||||
// It's ok to read the map directly since no write
|
||||
// will happen in the same time.
|
||||
<-cleaned |
||||
if len(tester.fetcher.announced) != 0 { |
||||
t.Fatalf("Announcement should be cleaned, got %d", len(tester.fetcher.announced)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestEnqueueTransactions(t *testing.T) { |
||||
tester := newTxFetcherTester() |
||||
txs := makeTransactions(tester.sender, txAnnounceLimit) |
||||
|
||||
done := make(chan struct{}) |
||||
tester.fetcher.importTxsHook = func(transactions []*types.Transaction) { |
||||
if len(transactions) == txAnnounceLimit { |
||||
done <- struct{}{} |
||||
} |
||||
} |
||||
go tester.fetcher.EnqueueTxs("peer", txs) |
||||
select { |
||||
case <-done: |
||||
case <-time.NewTimer(time.Second).C: |
||||
t.Fatalf("timeout") |
||||
} |
||||
} |
||||
|
||||
func TestInvalidTxAnnounces(t *testing.T) { |
||||
tester := newTxFetcherTester() |
||||
|
||||
var txs []*types.Transaction |
||||
txs = append(txs, makeUnsignedTransactions(tester.sender, 1)...) |
||||
txs = append(txs, makeTransactions(tester.sender, 1)...) |
||||
|
||||
txFetcherFn := tester.makeTxFetcher("peer", txs) |
||||
|
||||
dropped := make(chan string, 1) |
||||
tester.fetcher.dropHook = func(s string) { dropped <- s } |
||||
|
||||
for _, tx := range txs { |
||||
tester.fetcher.Notify("peer", []common.Hash{tx.Hash()}, time.Now(), txFetcherFn) |
||||
} |
||||
select { |
||||
case s := <-dropped: |
||||
if s != "peer" { |
||||
t.Fatalf("invalid dropped peer") |
||||
} |
||||
case <-time.NewTimer(time.Second).C: |
||||
t.Fatalf("timeout") |
||||
} |
||||
} |
||||
|
||||
func TestRejectUnderpriced(t *testing.T) { |
||||
tester := newTxFetcherTester() |
||||
tester.priceLimit = big.NewInt(10000) |
||||
|
||||
done := make(chan struct{}) |
||||
tester.fetcher.importTxsHook = func([]*types.Transaction) { done <- struct{}{} } |
||||
reject := make(chan struct{}) |
||||
tester.fetcher.rejectUnderprice = func(common.Hash) { reject <- struct{}{} } |
||||
|
||||
tx := types.NewTransaction(0, common.Address{0x1, 0x2, 0x3}, big.NewInt(int64(100)), 100, big.NewInt(int64(100)), nil) |
||||
tx, _ = types.SignTx(tx, types.NewEIP155Signer(big.NewInt(1)), tester.sender) |
||||
txFetcherFn := tester.makeTxFetcher("peer", []*types.Transaction{tx}) |
||||
|
||||
// Send the announcement first time
|
||||
tester.fetcher.Notify("peer", []common.Hash{tx.Hash()}, time.Now().Add(-arriveTimeout), txFetcherFn) |
||||
<-done |
||||
|
||||
// Resend the announcement, shouldn't schedule fetching this time
|
||||
tester.fetcher.Notify("peer", []common.Hash{tx.Hash()}, time.Now().Add(-arriveTimeout), txFetcherFn) |
||||
select { |
||||
case <-reject: |
||||
case <-time.NewTimer(time.Second).C: |
||||
t.Fatalf("timeout") |
||||
} |
||||
} |
Loading…
Reference in new issue