From 482df9a524f36973d6be3367878965ac7889e994 Mon Sep 17 00:00:00 2001 From: Dang Nhat Trinh Date: Sat, 8 Jul 2023 02:54:17 +0700 Subject: [PATCH] [WIP] Fix eth downloader unit tests --- accounts/abi/bind/base.go | 2 +- contracts/chequebook/cheque.go | 9 +- contracts/randomize/randomize_test.go | 11 +- contracts/tests/Inherited_test.go | 8 +- eth/downloader/downloader.go | 40 +- eth/downloader/downloader_test.go | 650 ++++++++++++-------------- eth/downloader/queue.go | 1 + eth/downloader/testchain_test.go | 230 +++++++++ params/network_params.go | 12 + 9 files changed, 573 insertions(+), 390 deletions(-) create mode 100644 eth/downloader/testchain_test.go diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 8d5b31cd59..4fc30e778a 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -134,7 +134,7 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string, return err } var ( - msg = tomochain.CallMsg{From: opts.From, To: &c.address, Data: input, GasPrice: common.MinGasPrice, Gas: uint64(4200000)} + msg = tomochain.CallMsg{From: opts.From, To: &c.address, Data: input} ctx = ensureContext(opts.Context) code []byte output []byte diff --git a/contracts/chequebook/cheque.go b/contracts/chequebook/cheque.go index 71bcf8002a..fd4b12f997 100644 --- a/contracts/chequebook/cheque.go +++ b/contracts/chequebook/cheque.go @@ -344,7 +344,14 @@ func (self *Chequebook) Deposit(amount *big.Int) (string, error) { // The caller must hold self.lock. func (self *Chequebook) deposit(amount *big.Int) (string, error) { // since the amount is variable here, we do not use sessions - depositTransactor := bind.NewKeyedTransactor(self.prvKey) + chainID, err := self.backend.ChainID(context.Background()) + if err != nil { + return "", err + } + depositTransactor, err := bind.NewKeyedTransactorWithChainID(self.prvKey, chainID) + if err != nil { + return "", err + } depositTransactor.Value = amount chbookRaw := &contract.ChequebookRaw{Contract: self.contract} tx, err := chbookRaw.Transfer(depositTransactor) diff --git a/contracts/randomize/randomize_test.go b/contracts/randomize/randomize_test.go index 87320dde05..ce4722bb7f 100644 --- a/contracts/randomize/randomize_test.go +++ b/contracts/randomize/randomize_test.go @@ -57,7 +57,6 @@ func TestRandomize(t *testing.T) { transactOpts.GasLimit = 1000000 randomizeAddress, randomize, err := DeployRandomize(transactOpts, contractBackend) - t.Log("contract address", randomizeAddress.String()) if err != nil { t.Fatalf("can't deploy root registry: %v", err) } @@ -66,18 +65,15 @@ func TestRandomize(t *testing.T) { d := time.Now().Add(1000 * time.Millisecond) ctx, cancel := context.WithDeadline(context.Background(), d) defer cancel() - code, _ := contractBackend.CodeAt(ctx, randomizeAddress, nil) - t.Log("contract code", common.ToHex(code)) + contractBackend.CodeAt(ctx, randomizeAddress, nil) f := func(key, val common.Hash) bool { - t.Log(key.Hex(), val.Hex()) return true } contractBackend.ForEachStorageAt(ctx, randomizeAddress, nil, f) - s, err := randomize.SetSecret(byte0) + _, err = randomize.SetSecret(byte0) if err != nil { t.Fatalf("can't set secret: %v", err) } - t.Log("tx data", s) contractBackend.Commit() } @@ -151,8 +147,7 @@ func TestSendTxRandomizeSecretAndOpening(t *testing.T) { if err != nil { t.Fatalf("Can't get secret from SC: %v", err) } - randomize, err := contracts.DecryptRandomizeFromSecretsAndOpening(secrets, opening) - t.Log("randomize", randomize) + _, err = contracts.DecryptRandomizeFromSecretsAndOpening(secrets, opening) if err != nil { t.Error("Can't decrypt secret and opening", err) } diff --git a/contracts/tests/Inherited_test.go b/contracts/tests/Inherited_test.go index 90dbf4c0c1..fd858cbf32 100644 --- a/contracts/tests/Inherited_test.go +++ b/contracts/tests/Inherited_test.go @@ -2,7 +2,6 @@ package tests import ( "context" - "fmt" "math/big" "os" "testing" @@ -38,15 +37,12 @@ func TestPriceFeed(t *testing.T) { t.Fatalf("can't create TransactOpts: %v", err) } // deploy payer swap SMC - addr, contract, err := DeployMyInherited(transactOpts, contractBackend) + _, contract, err := DeployMyInherited(transactOpts, contractBackend) if err != nil { t.Fatal("can't deploy smart contract: ", err) } - fmt.Println("addr", addr.Hex()) - tx, err := contract.Foo() + _, err = contract.Foo() if err != nil { t.Fatal("can't run function Foo() in smart contract: ", err) } - fmt.Println("tx", tx) - } diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index eba7fad779..98297014a0 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -56,9 +56,11 @@ var ( qosConfidenceCap = 10 // Number of peers above which not to modify RTT confidence qosTuningImpact = 0.25 // Impact that a new tuning target has on the previous value - maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) - maxHeadersProcess = 2048 // Number of header download results to import at once into the chain - maxResultsProcess = 2048 // Number of content download results to import at once into the chain + maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) + maxHeadersProcess = 2048 // Number of header download results to import at once into the chain + maxResultsProcess = 2048 // Number of content download results to import at once into the chain + fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) + lightMaxForkAncestry uint64 = params.LightImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) fsHeaderCheckFrequency = 100 // Verification frequency of the downloaded headers during fast sync fsHeaderSafetyNet = 2048 // Number of headers to discard in case a chain violation is detected @@ -975,22 +977,22 @@ func (d *Downloader) fetchReceipts(from uint64) error { // various callbacks to handle the slight differences between processing them. // // The instrumentation parameters: -// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) -// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) -// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) -// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) -// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) -// - pending: task callback for the number of requests still needing download (detect completion/non-completability) -// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) -// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) -// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) -// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) -// - fetch: network callback to actually send a particular download request to a physical remote peer -// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) -// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) -// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks -// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) -// - kind: textual label of the type being downloaded to display in log mesages +// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) +// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) +// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) +// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) +// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) +// - pending: task callback for the number of requests still needing download (detect completion/non-completability) +// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) +// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) +// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) +// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) +// - fetch: network callback to actually send a particular download request to a physical remote peer +// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) +// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) +// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks +// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) +// - kind: textual label of the type being downloaded to display in log mesages func (d *Downloader) fetchParts(errCancel error, deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool, expire func() map[string]int, pending func() int, inFlight func() bool, throttle func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, error), fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int, diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index ae993b26bd..4145926173 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -25,23 +25,19 @@ import ( "testing" "time" + "github.com/tomochain/tomochain" "github.com/tomochain/tomochain/common" "github.com/tomochain/tomochain/consensus/ethash" "github.com/tomochain/tomochain/core" "github.com/tomochain/tomochain/core/rawdb" "github.com/tomochain/tomochain/core/types" - "github.com/tomochain/tomochain/crypto" + "github.com/tomochain/tomochain/core/vm" "github.com/tomochain/tomochain/ethdb" "github.com/tomochain/tomochain/event" "github.com/tomochain/tomochain/params" "github.com/tomochain/tomochain/trie" ) -var ( - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testAddress = crypto.PubkeyToAddress(testKey.PublicKey) -) - // Reduce some of the parameters to make the tester faster. func init() { MaxForkAncestry = uint64(10000) @@ -71,34 +67,37 @@ type downloadTester struct { peerMissingStates map[string]map[common.Hash]bool // State entries that fast sync should not return - lock sync.RWMutex + chain *core.BlockChain + peers map[string]*downloadTesterPeer + lock sync.RWMutex } // newTester creates a new downloader test mocker. -func newTester() *downloadTester { - testdb := rawdb.NewMemoryDatabase() - genesis := core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000)) +func newTester(t *testing.T) *downloadTester { + return newTesterWithNotification(t, nil) +} +// newTester creates a new downloader test mocker. +func newTesterWithNotification(t *testing.T, success func()) *downloadTester { + db := rawdb.NewMemoryDatabase() + t.Cleanup(func() { + db.Close() + }) + gspec := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + gspec.MustCommit(db) + chain, err := core.NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}) + if err != nil { + panic(err) + } tester := &downloadTester{ - genesis: genesis, - peerDb: testdb, - ownHashes: []common.Hash{genesis.Hash()}, - ownHeaders: map[common.Hash]*types.Header{genesis.Hash(): genesis.Header()}, - ownBlocks: map[common.Hash]*types.Block{genesis.Hash(): genesis}, - ownReceipts: map[common.Hash]types.Receipts{genesis.Hash(): nil}, - ownChainTd: map[common.Hash]*big.Int{genesis.Hash(): genesis.Difficulty()}, - peerHashes: make(map[string][]common.Hash), - peerHeaders: make(map[string]map[common.Hash]*types.Header), - peerBlocks: make(map[string]map[common.Hash]*types.Block), - peerReceipts: make(map[string]map[common.Hash]types.Receipts), - peerChainTds: make(map[string]map[common.Hash]*big.Int), - peerMissingStates: make(map[string]map[common.Hash]bool), - } - tester.stateDb = rawdb.NewMemoryDatabase() - tester.stateDb.Put(genesis.Root().Bytes(), []byte{0x00}) - - tester.downloader = New(FullSync, tester.stateDb, new(event.TypeMux), tester, nil, tester.dropPeer) - + chain: chain, + peers: make(map[string]*downloadTesterPeer), + } + tester.downloader = New(FullSync, db, new(event.TypeMux), tester.chain, nil, tester.dropPeer) return tester } @@ -388,8 +387,22 @@ func (dl *downloadTester) Rollback(hashes []common.Hash) { } // newPeer registers a new block download source into the downloader. -func (dl *downloadTester) newPeer(id string, version int, hashes []common.Hash, headers map[common.Hash]*types.Header, blocks map[common.Hash]*types.Block, receipts map[common.Hash]types.Receipts) error { - return dl.newSlowPeer(id, version, hashes, headers, blocks, receipts, 0) +func (dl *downloadTester) newPeer(id string, version uint, blocks []*types.Block) *downloadTesterPeer { + dl.lock.Lock() + defer dl.lock.Unlock() + + peer := &downloadTesterPeer{ + dl: dl, + id: id, + chain: newTestBlockchain(blocks), + withholdHeaders: make(map[common.Hash]struct{}), + } + dl.peers[id] = peer + + if err := dl.downloader.RegisterPeer(id, int(version), peer); err != nil { + panic(err) + } + return peer } // newSlowPeer registers a new block download source into the downloader, with a @@ -461,10 +474,12 @@ func (dl *downloadTester) dropPeer(id string) { func (dl *downloadTester) Config() *params.ChainConfig { return params.TestChainConfig } type downloadTesterPeer struct { - dl *downloadTester - id string - delay time.Duration - lock sync.RWMutex + dl *downloadTester + id string + chain *core.BlockChain + delay time.Duration + lock sync.RWMutex + withholdHeaders map[common.Hash]struct{} } // setDelay is a thread safe setter for the network delay value. @@ -672,23 +687,21 @@ func TestCanonicalSynchronisation64Light(t *testing.T) { testCanonicalSynchronisation(t, 64, LightSync) } -func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) { +func testCanonicalSynchronisation(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() - tester := newTester() + tester := newTester(t) defer tester.terminate() // Create a small enough block chain to download - targetBlocks := blockCacheItems - 15 - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) - - tester.newPeer("peer", protocol, hashes, headers, blocks, receipts) + chain := testChainBase.shorten(blockCacheMaxItems - 15) + tester.newPeer("peer", protocol, chain.blocks[1:]) // Synchronise with the peer and make sure all relevant data was retrieved if err := tester.sync("peer", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnChain(t, tester, targetBlocks+1) + assertOwnChain(t, tester, len(chain.blocks)) } // Tests that if a large batch of blocks are being downloaded, it is throttled @@ -699,16 +712,14 @@ func TestThrottling63Fast(t *testing.T) { testThrottling(t, 63, FastSync) } func TestThrottling64Full(t *testing.T) { testThrottling(t, 64, FullSync) } func TestThrottling64Fast(t *testing.T) { testThrottling(t, 64, FastSync) } -func testThrottling(t *testing.T, protocol int, mode SyncMode) { +func testThrottling(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() - tester := newTester() + tester := newTester(t) defer tester.terminate() // Create a long block chain to download and the tester - targetBlocks := 8 * blockCacheItems - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) - - tester.newPeer("peer", protocol, hashes, headers, blocks, receipts) + targetBlocks := len(testChainBase.blocks) - 1 + tester.newPeer("peer", protocol, testChainBase.blocks[1:]) // Wrap the importer to allow stepping blocked, proceed := uint32(0), make(chan struct{}) @@ -786,30 +797,29 @@ func TestForkedSync64Full(t *testing.T) { testForkedSync(t, 64, FullSync) } func TestForkedSync64Fast(t *testing.T) { testForkedSync(t, 64, FastSync) } func TestForkedSync64Light(t *testing.T) { testForkedSync(t, 64, LightSync) } -func testForkedSync(t *testing.T, protocol int, mode SyncMode) { +func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { t.Parallel() - tester := newTester() + tester := newTester(t) defer tester.terminate() // Create a long enough forked chain - common, fork := MaxHashFetch, 2*MaxHashFetch - hashesA, hashesB, headersA, headersB, blocksA, blocksB, receiptsA, receiptsB := tester.makeChainFork(common+fork, fork, tester.genesis, nil, true) - - tester.newPeer("fork A", protocol, hashesA, headersA, blocksA, receiptsA) - tester.newPeer("fork B", protocol, hashesB, headersB, blocksB, receiptsB) + chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + 80) + chainB := testChainForkLightB.shorten(len(testChainBase.blocks) + 81) + tester.newPeer("fork A", protocol, chainA.blocks[1:]) + tester.newPeer("fork B", protocol, chainB.blocks[1:]) // Synchronise with the peer and make sure all blocks were retrieved if err := tester.sync("fork A", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnChain(t, tester, common+fork+1) + assertOwnChain(t, tester, len(chainA.blocks)) // Synchronise with the second peer and make sure that fork is pulled too if err := tester.sync("fork B", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnForkedChain(t, tester, common+1, []int{common + fork + 1, common + fork + 1}) + assertOwnChain(t, tester, len(chainB.blocks)) } // Tests that synchronising against a much shorter but much heavyer fork works @@ -821,30 +831,26 @@ func TestHeavyForkedSync64Full(t *testing.T) { testHeavyForkedSync(t, 64, FullS func TestHeavyForkedSync64Fast(t *testing.T) { testHeavyForkedSync(t, 64, FastSync) } func TestHeavyForkedSync64Light(t *testing.T) { testHeavyForkedSync(t, 64, LightSync) } -func testHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() - // Create a long enough forked chain - common, fork := MaxHashFetch, 4*MaxHashFetch - hashesA, hashesB, headersA, headersB, blocksA, blocksB, receiptsA, receiptsB := tester.makeChainFork(common+fork, fork, tester.genesis, nil, false) - - tester.newPeer("light", protocol, hashesA, headersA, blocksA, receiptsA) - tester.newPeer("heavy", protocol, hashesB[fork/2:], headersB, blocksB, receiptsB) + chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + 80) + chainB := testChainForkHeavy.shorten(len(testChainBase.blocks) + 79) + tester.newPeer("light", protocol, chainA.blocks[1:]) + tester.newPeer("heavy", protocol, chainB.blocks[1:]) // Synchronise with the peer and make sure all blocks were retrieved if err := tester.sync("light", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnChain(t, tester, common+fork+1) + assertOwnChain(t, tester, len(chainA.blocks)) // Synchronise with the second peer and make sure that fork is pulled too if err := tester.sync("heavy", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnForkedChain(t, tester, common+1, []int{common + fork + 1, common + fork/2 + 1}) + assertOwnChain(t, tester, len(chainB.blocks)) } // Tests that chain forks are contained within a certain interval of the current @@ -857,24 +863,20 @@ func TestBoundedForkedSync64Full(t *testing.T) { testBoundedForkedSync(t, 64, F func TestBoundedForkedSync64Fast(t *testing.T) { testBoundedForkedSync(t, 64, FastSync) } func TestBoundedForkedSync64Light(t *testing.T) { testBoundedForkedSync(t, 64, LightSync) } -func testBoundedForkedSync(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() - // Create a long enough forked chain - common, fork := 13, int(MaxForkAncestry+17) - hashesA, hashesB, headersA, headersB, blocksA, blocksB, receiptsA, receiptsB := tester.makeChainFork(common+fork, fork, tester.genesis, nil, true) - - tester.newPeer("original", protocol, hashesA, headersA, blocksA, receiptsA) - tester.newPeer("rewriter", protocol, hashesB, headersB, blocksB, receiptsB) + chainA := testChainForkLightA + chainB := testChainForkLightB + tester.newPeer("original", protocol, chainA.blocks[1:]) + tester.newPeer("rewriter", protocol, chainB.blocks[1:]) // Synchronise with the peer and make sure all blocks were retrieved if err := tester.sync("original", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnChain(t, tester, common+fork+1) + assertOwnChain(t, tester, len(chainA.blocks)) // Synchronise with the second peer and ensure that the fork is rejected to being too old if err := tester.sync("rewriter", nil, mode); err != errInvalidAncestor { @@ -892,25 +894,22 @@ func TestBoundedHeavyForkedSync64Full(t *testing.T) { testBoundedHeavyForkedSyn func TestBoundedHeavyForkedSync64Fast(t *testing.T) { testBoundedHeavyForkedSync(t, 64, FastSync) } func TestBoundedHeavyForkedSync64Light(t *testing.T) { testBoundedHeavyForkedSync(t, 64, LightSync) } -func testBoundedHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() // Create a long enough forked chain - common, fork := 13, int(MaxForkAncestry+17) - hashesA, hashesB, headersA, headersB, blocksA, blocksB, receiptsA, receiptsB := tester.makeChainFork(common+fork, fork, tester.genesis, nil, false) - - tester.newPeer("original", protocol, hashesA, headersA, blocksA, receiptsA) - tester.newPeer("heavy-rewriter", protocol, hashesB[MaxForkAncestry-17:], headersB, blocksB, receiptsB) // Root the fork below the ancestor limit + chainA := testChainForkLightA + chainB := testChainForkHeavy + tester.newPeer("original", protocol, chainA.blocks[1:]) // Synchronise with the peer and make sure all blocks were retrieved if err := tester.sync("original", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnChain(t, tester, common+fork+1) + assertOwnChain(t, tester, len(chainA.blocks)) + tester.newPeer("heavy-rewriter", protocol, chainB.blocks[1:]) // Synchronise with the second peer and ensure that the fork is rejected to being too old if err := tester.sync("heavy-rewriter", nil, mode); err != errInvalidAncestor { t.Fatalf("sync failure mismatch: have %v, want %v", err, errInvalidAncestor) @@ -922,7 +921,7 @@ func testBoundedHeavyForkedSync(t *testing.T, protocol int, mode SyncMode) { func TestInactiveDownloader62(t *testing.T) { t.Parallel() - tester := newTester() + tester := newTester(t) defer tester.terminate() // Check that neither block headers nor bodies are accepted @@ -939,7 +938,7 @@ func TestInactiveDownloader62(t *testing.T) { func TestInactiveDownloader63(t *testing.T) { t.Parallel() - tester := newTester() + tester := newTester(t) defer tester.terminate() // Check that neither block headers nor bodies are accepted @@ -962,23 +961,12 @@ func TestCancel64Full(t *testing.T) { testCancel(t, 64, FullSync) } func TestCancel64Fast(t *testing.T) { testCancel(t, 64, FastSync) } func TestCancel64Light(t *testing.T) { testCancel(t, 64, LightSync) } -func testCancel(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testCancel(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() - // Create a small enough block chain to download and the tester - targetBlocks := blockCacheItems - 15 - if targetBlocks >= MaxHashFetch { - targetBlocks = MaxHashFetch - 15 - } - if targetBlocks >= MaxHeaderFetch { - targetBlocks = MaxHeaderFetch - 15 - } - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) - - tester.newPeer("peer", protocol, hashes, headers, blocks, receipts) + chain := testChainBase.shorten(MaxHeaderFetch) + tester.newPeer("peer", protocol, chain.blocks[1:]) // Make sure canceling works with a pristine downloader tester.downloader.Cancel() @@ -1003,25 +991,22 @@ func TestMultiSynchronisation64Full(t *testing.T) { testMultiSynchronisation(t, func TestMultiSynchronisation64Fast(t *testing.T) { testMultiSynchronisation(t, 64, FastSync) } func TestMultiSynchronisation64Light(t *testing.T) { testMultiSynchronisation(t, 64, LightSync) } -func testMultiSynchronisation(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() // Create various peers with various parts of the chain targetPeers := 8 - targetBlocks := targetPeers*blockCacheItems - 15 - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) + chain := testChainBase.shorten(targetPeers * 100) for i := 0; i < targetPeers; i++ { id := fmt.Sprintf("peer #%d", i) - tester.newPeer(id, protocol, hashes[i*blockCacheItems:], headers, blocks, receipts) + tester.newPeer(id, protocol, chain.shorten(len(chain.blocks) / (i + 1)).blocks[1:]) } if err := tester.sync("peer #0", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnChain(t, tester, targetBlocks+1) + assertOwnChain(t, tester, len(chain.blocks)) } // Tests that synchronisations behave well in multi-version protocol environments @@ -1033,31 +1018,28 @@ func TestMultiProtoSynchronisation64Full(t *testing.T) { testMultiProtoSync(t, func TestMultiProtoSynchronisation64Fast(t *testing.T) { testMultiProtoSync(t, 64, FastSync) } func TestMultiProtoSynchronisation64Light(t *testing.T) { testMultiProtoSync(t, 64, LightSync) } -func testMultiProtoSync(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() // Create a small enough block chain to download - targetBlocks := blockCacheItems - 15 - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) + chain := testChainBase.shorten(blockCacheMaxItems - 15) // Create peers of every type - tester.newPeer("peer 62", 62, hashes, headers, blocks, nil) - tester.newPeer("peer 63", 63, hashes, headers, blocks, receipts) - tester.newPeer("peer 64", 64, hashes, headers, blocks, receipts) + tester.newPeer("peer 62", 62, chain.blocks[1:]) + tester.newPeer("peer 63", 63, chain.blocks[1:]) + tester.newPeer("peer 64", 64, chain.blocks[1:]) // Synchronise with the requested peer and make sure all blocks were retrieved if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnChain(t, tester, targetBlocks+1) + assertOwnChain(t, tester, len(chain.blocks)) // Check that no peers have been dropped off - for _, version := range []int{62, 63, 64} { + for _, version := range []int{66, 67} { peer := fmt.Sprintf("peer %d", version) - if _, ok := tester.peerHashes[peer]; !ok { + if _, ok := tester.peers[peer]; !ok { t.Errorf("%s dropped", peer) } } @@ -1072,17 +1054,13 @@ func TestEmptyShortCircuit64Full(t *testing.T) { testEmptyShortCircuit(t, 64, F func TestEmptyShortCircuit64Fast(t *testing.T) { testEmptyShortCircuit(t, 64, FastSync) } func TestEmptyShortCircuit64Light(t *testing.T) { testEmptyShortCircuit(t, 64, LightSync) } -func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() // Create a block chain to download - targetBlocks := 2*blockCacheItems - 15 - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) - - tester.newPeer("peer", protocol, hashes, headers, blocks, receipts) + chain := testChainBase + tester.newPeer("peer", protocol, chain.blocks[1:]) // Instrument the downloader to signal body requests bodiesHave, receiptsHave := int32(0), int32(0) @@ -1096,17 +1074,17 @@ func testEmptyShortCircuit(t *testing.T, protocol int, mode SyncMode) { if err := tester.sync("peer", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnChain(t, tester, targetBlocks+1) + assertOwnChain(t, tester, len(chain.blocks)) // Validate the number of block bodies that should have been requested bodiesNeeded, receiptsNeeded := 0, 0 - for _, block := range blocks { - if mode != LightSync && block != tester.genesis && (len(block.Transactions()) > 0 || len(block.Uncles()) > 0) { + for _, block := range chain.blocks[1:] { + if mode != LightSync && (len(block.Transactions()) > 0 || len(block.Uncles()) > 0) { bodiesNeeded++ } } - for _, receipt := range receipts { - if mode == FastSync && len(receipt) > 0 { + for _, block := range chain.blocks[1:] { + if mode == FastSync && len(block.Transactions()) > 0 { receiptsNeeded++ } } @@ -1127,30 +1105,24 @@ func TestMissingHeaderAttack64Full(t *testing.T) { testMissingHeaderAttack(t, 6 func TestMissingHeaderAttack64Fast(t *testing.T) { testMissingHeaderAttack(t, 64, FastSync) } func TestMissingHeaderAttack64Light(t *testing.T) { testMissingHeaderAttack(t, 64, LightSync) } -func testMissingHeaderAttack(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() - // Create a small enough block chain to download - targetBlocks := blockCacheItems - 15 - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) + chain := testChainBase.shorten(blockCacheMaxItems - 15) - // Attempt a full sync with an attacker feeding gapped headers - tester.newPeer("attack", protocol, hashes, headers, blocks, receipts) - missing := targetBlocks / 2 - delete(tester.peerHeaders["attack"], hashes[missing]) + attacker := tester.newPeer("attack", protocol, chain.blocks[1:]) + attacker.withholdHeaders[chain.blocks[len(chain.blocks)/2-1].Hash()] = struct{}{} if err := tester.sync("attack", nil, mode); err == nil { t.Fatalf("succeeded attacker synchronisation") } // Synchronise with the valid peer and make sure sync succeeds - tester.newPeer("valid", protocol, hashes, headers, blocks, receipts) + tester.newPeer("valid", protocol, chain.blocks[1:]) if err := tester.sync("valid", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnChain(t, tester, targetBlocks+1) + assertOwnChain(t, tester, len(chain.blocks)) } // Tests that if requested headers are shifted (i.e. first is missing), the queue @@ -1162,31 +1134,25 @@ func TestShiftedHeaderAttack64Full(t *testing.T) { testShiftedHeaderAttack(t, 6 func TestShiftedHeaderAttack64Fast(t *testing.T) { testShiftedHeaderAttack(t, 64, FastSync) } func TestShiftedHeaderAttack64Light(t *testing.T) { testShiftedHeaderAttack(t, 64, LightSync) } -func testShiftedHeaderAttack(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() - // Create a small enough block chain to download - targetBlocks := blockCacheItems - 15 - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) + chain := testChainBase.shorten(blockCacheMaxItems - 15) // Attempt a full sync with an attacker feeding shifted headers - tester.newPeer("attack", protocol, hashes, headers, blocks, receipts) - delete(tester.peerHeaders["attack"], hashes[len(hashes)-2]) - delete(tester.peerBlocks["attack"], hashes[len(hashes)-2]) - delete(tester.peerReceipts["attack"], hashes[len(hashes)-2]) + attacker := tester.newPeer("attack", protocol, chain.blocks[1:]) + attacker.withholdHeaders[chain.blocks[1].Hash()] = struct{}{} if err := tester.sync("attack", nil, mode); err == nil { t.Fatalf("succeeded attacker synchronisation") } // Synchronise with the valid peer and make sure sync succeeds - tester.newPeer("valid", protocol, hashes, headers, blocks, receipts) + tester.newPeer("valid", protocol, chain.blocks[1:]) if err := tester.sync("valid", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - assertOwnChain(t, tester, targetBlocks+1) + assertOwnChain(t, tester, len(chain.blocks)) } // Tests that upon detecting an invalid header, the recent ones are rolled back @@ -1196,87 +1162,78 @@ func TestInvalidHeaderRollback63Fast(t *testing.T) { testInvalidHeaderRollback( func TestInvalidHeaderRollback64Fast(t *testing.T) { testInvalidHeaderRollback(t, 64, FastSync) } func TestInvalidHeaderRollback64Light(t *testing.T) { testInvalidHeaderRollback(t, 64, LightSync) } -func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() // Create a small enough block chain to download targetBlocks := 3*fsHeaderSafetyNet + 256 + fsMinFullBlocks - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) + chain := testChainBase.shorten(targetBlocks) // Attempt to sync with an attacker that feeds junk during the fast sync phase. // This should result in the last fsHeaderSafetyNet headers being rolled back. - tester.newPeer("fast-attack", protocol, hashes, headers, blocks, receipts) missing := fsHeaderSafetyNet + MaxHeaderFetch + 1 - delete(tester.peerHeaders["fast-attack"], hashes[len(hashes)-missing]) + + fastAttacker := tester.newPeer("fast-attack", protocol, chain.blocks[1:]) + fastAttacker.withholdHeaders[chain.blocks[missing].Hash()] = struct{}{} if err := tester.sync("fast-attack", nil, mode); err == nil { t.Fatalf("succeeded fast attacker synchronisation") } - if head := tester.CurrentHeader().Number.Int64(); int(head) > MaxHeaderFetch { + if head := tester.chain.CurrentHeader().Number.Int64(); int(head) > MaxHeaderFetch { t.Errorf("rollback head mismatch: have %v, want at most %v", head, MaxHeaderFetch) } // Attempt to sync with an attacker that feeds junk during the block import phase. // This should result in both the last fsHeaderSafetyNet number of headers being // rolled back, and also the pivot point being reverted to a non-block status. - tester.newPeer("block-attack", protocol, hashes, headers, blocks, receipts) missing = 3*fsHeaderSafetyNet + MaxHeaderFetch + 1 - delete(tester.peerHeaders["fast-attack"], hashes[len(hashes)-missing]) // Make sure the fast-attacker doesn't fill in - delete(tester.peerHeaders["block-attack"], hashes[len(hashes)-missing]) + + blockAttacker := tester.newPeer("block-attack", protocol, chain.blocks[1:]) + fastAttacker.withholdHeaders[chain.blocks[missing].Hash()] = struct{}{} // Make sure the fast-attacker doesn't fill in + blockAttacker.withholdHeaders[chain.blocks[missing].Hash()] = struct{}{} if err := tester.sync("block-attack", nil, mode); err == nil { t.Fatalf("succeeded block attacker synchronisation") } - if head := tester.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { + if head := tester.chain.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) } if mode == FastSync { - if head := tester.CurrentBlock().NumberU64(); head != 0 { + if head := tester.chain.CurrentBlock().Number().Uint64(); head != 0 { t.Errorf("fast sync pivot block #%d not rolled back", head) } } // Attempt to sync with an attacker that withholds promised blocks after the // fast sync pivot point. This could be a trial to leave the node with a bad // but already imported pivot block. - tester.newPeer("withhold-attack", protocol, hashes, headers, blocks, receipts) - missing = 3*fsHeaderSafetyNet + MaxHeaderFetch + 1 + withholdAttacker := tester.newPeer("withhold-attack", protocol, chain.blocks[1:]) tester.downloader.syncInitHook = func(uint64, uint64) { - for i := missing; i <= len(hashes); i++ { - delete(tester.peerHeaders["withhold-attack"], hashes[len(hashes)-i]) + for i := missing; i < len(chain.blocks); i++ { + withholdAttacker.withholdHeaders[chain.blocks[i].Hash()] = struct{}{} } tester.downloader.syncInitHook = nil } - if err := tester.sync("withhold-attack", nil, mode); err == nil { t.Fatalf("succeeded withholding attacker synchronisation") } - if head := tester.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { + if head := tester.chain.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) } if mode == FastSync { - if head := tester.CurrentBlock().NumberU64(); head != 0 { + if head := tester.chain.CurrentBlock().Number().Uint64(); head != 0 { t.Errorf("fast sync pivot block #%d not rolled back", head) } } - // Synchronise with the valid peer and make sure sync succeeds. Since the last - // rollback should also disable fast syncing for this process, verify that we - // did a fresh full sync. Note, we can't assert anything about the receipts - // since we won't purge the database of them, hence we can't use assertOwnChain. - tester.newPeer("valid", protocol, hashes, headers, blocks, receipts) + // Synchronise with the valid peer and make sure sync succeeds. Since the last rollback + // should also disable fast syncing for this process, verify that we did a fresh full + // sync. Note, we can't assert anything about the receipts since we won't purge the + // database of them, hence we can't use assertOwnChain. + tester.newPeer("valid", protocol, chain.blocks[1:]) if err := tester.sync("valid", nil, mode); err != nil { t.Fatalf("failed to synchronise blocks: %v", err) } - if hs := len(tester.ownHeaders); hs != len(headers) { - t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, len(headers)) - } - if mode != LightSync { - if bs := len(tester.ownBlocks); bs != len(blocks) { - t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, len(blocks)) - } - } + assertOwnChain(t, tester, len(chain.blocks)) } // Tests that a peer advertising an high TD doesn't get to stall the downloader @@ -1288,15 +1245,12 @@ func TestHighTDStarvationAttack64Full(t *testing.T) { testHighTDStarvationAttac func TestHighTDStarvationAttack64Fast(t *testing.T) { testHighTDStarvationAttack(t, 64, FastSync) } func TestHighTDStarvationAttack64Light(t *testing.T) { testHighTDStarvationAttack(t, 64, LightSync) } -func testHighTDStarvationAttack(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() - hashes, headers, blocks, receipts := tester.makeChain(0, 0, tester.genesis, nil, false) - tester.newPeer("attack", protocol, []common.Hash{hashes[0]}, headers, blocks, receipts) - + chain := testChainBase.shorten(1) + tester.newPeer("attack", protocol, chain.blocks[1:]) if err := tester.sync("attack", big.NewInt(1000000), mode); err != errStallingPeer { t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errStallingPeer) } @@ -1307,7 +1261,7 @@ func TestBlockHeaderAttackerDropping62(t *testing.T) { testBlockHeaderAttackerDr func TestBlockHeaderAttackerDropping63(t *testing.T) { testBlockHeaderAttackerDropping(t, 63) } func TestBlockHeaderAttackerDropping64(t *testing.T) { testBlockHeaderAttackerDropping(t, 64) } -func testBlockHeaderAttackerDropping(t *testing.T, protocol int) { +func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { t.Parallel() // Define the disconnection requirement for individual hash fetch errors @@ -1337,16 +1291,15 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol int) { {errCancelContentProcessing, false}, // Synchronisation was canceled, origin may be innocent, don't drop } // Run the tests and check disconnection status - tester := newTester() + tester := newTester(t) defer tester.terminate() + chain := testChainBase.shorten(1) for i, tt := range tests { - // Register a new peer and ensure it's presence + // Register a new peer and ensure its presence id := fmt.Sprintf("test %d", i) - if err := tester.newPeer(id, protocol, []common.Hash{tester.genesis.Hash()}, nil, nil, nil); err != nil { - t.Fatalf("test %d: failed to register new peer: %v", i, err) - } - if _, ok := tester.peerHashes[id]; !ok { + tester.newPeer(id, protocol, chain.blocks[1:]) + if _, ok := tester.peers[id]; !ok { t.Fatalf("test %d: registered peer not found", i) } // Simulate a synchronisation and check the required result @@ -1368,15 +1321,11 @@ func TestSyncProgress64Full(t *testing.T) { testSyncProgress(t, 64, FullSync) } func TestSyncProgress64Fast(t *testing.T) { testSyncProgress(t, 64, FastSync) } func TestSyncProgress64Light(t *testing.T) { testSyncProgress(t, 64, LightSync) } -func testSyncProgress(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() - // Create a small enough block chain to download - targetBlocks := blockCacheItems - 15 - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) + chain := testChainBase.shorten(blockCacheMaxItems - 15) // Set a sync init hook to catch progress changes starting := make(chan struct{}) @@ -1386,12 +1335,10 @@ func testSyncProgress(t *testing.T, protocol int, mode SyncMode) { starting <- struct{}{} <-progress } - // Retrieve the sync progress and ensure they are zero (pristine sync) - if progress := tester.downloader.Progress(); progress.StartingBlock != 0 || progress.CurrentBlock != 0 || progress.HighestBlock != 0 { - t.Fatalf("Pristine progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, 0, 0, 0) - } + checkProgress(t, tester.downloader, "pristine", tomochain.SyncProgress{}) + // Synchronise half the blocks and check initial progress - tester.newPeer("peer-half", protocol, hashes[targetBlocks/2:], headers, blocks, receipts) + tester.newPeer("peer-half", protocol, chain.shorten(len(chain.blocks) / 2).blocks[1:]) pending := new(sync.WaitGroup) pending.Add(1) @@ -1402,16 +1349,15 @@ func testSyncProgress(t *testing.T, protocol int, mode SyncMode) { } }() <-starting - if progress := tester.downloader.Progress(); progress.StartingBlock != 0 || progress.CurrentBlock != 0 || progress.HighestBlock != uint64(targetBlocks/2+1) { - t.Fatalf("Initial progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, 0, 0, targetBlocks/2+1) - } + checkProgress(t, tester.downloader, "initial", tomochain.SyncProgress{ + HighestBlock: uint64(len(chain.blocks)/2 - 1), + }) progress <- struct{}{} pending.Wait() // Synchronise all the blocks and check continuation progress - tester.newPeer("peer-full", protocol, hashes, headers, blocks, receipts) + tester.newPeer("peer-full", protocol, chain.blocks[1:]) pending.Add(1) - go func() { defer pending.Done() if err := tester.sync("peer-full", nil, mode); err != nil { @@ -1419,15 +1365,29 @@ func testSyncProgress(t *testing.T, protocol int, mode SyncMode) { } }() <-starting - if progress := tester.downloader.Progress(); progress.StartingBlock != uint64(targetBlocks/2+1) || progress.CurrentBlock != uint64(targetBlocks/2+1) || progress.HighestBlock != uint64(targetBlocks) { - t.Fatalf("Completing progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, targetBlocks/2+1, targetBlocks/2+1, targetBlocks) - } + checkProgress(t, tester.downloader, "completing", tomochain.SyncProgress{ + StartingBlock: uint64(len(chain.blocks)/2 - 1), + CurrentBlock: uint64(len(chain.blocks)/2 - 1), + HighestBlock: uint64(len(chain.blocks) - 1), + }) + + // Check final progress after successful sync progress <- struct{}{} pending.Wait() + checkProgress(t, tester.downloader, "final", tomochain.SyncProgress{ + StartingBlock: uint64(len(chain.blocks)/2 - 1), + CurrentBlock: uint64(len(chain.blocks) - 1), + HighestBlock: uint64(len(chain.blocks) - 1), + }) +} - // Check final progress after successful sync - if progress := tester.downloader.Progress(); progress.StartingBlock != uint64(targetBlocks/2+1) || progress.CurrentBlock != uint64(targetBlocks) || progress.HighestBlock != uint64(targetBlocks) { - t.Fatalf("Final progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, targetBlocks/2+1, targetBlocks, targetBlocks) +func checkProgress(t *testing.T, d *Downloader, stage string, want tomochain.SyncProgress) { + // Mark this method as a helper to report errors at callsite, not in here + t.Helper() + + p := d.Progress() + if p.StartingBlock != want.StartingBlock || p.CurrentBlock != want.CurrentBlock || p.HighestBlock != want.HighestBlock { + t.Fatalf("%s progress mismatch:\nhave %+v\nwant %+v", stage, p, want) } } @@ -1441,15 +1401,12 @@ func TestForkedSyncProgress64Full(t *testing.T) { testForkedSyncProgress(t, 64, func TestForkedSyncProgress64Fast(t *testing.T) { testForkedSyncProgress(t, 64, FastSync) } func TestForkedSyncProgress64Light(t *testing.T) { testForkedSyncProgress(t, 64, LightSync) } -func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() - // Create a forked chain to simulate origin revertal - common, fork := MaxHashFetch, 2*MaxHashFetch - hashesA, hashesB, headersA, headersB, blocksA, blocksB, receiptsA, receiptsB := tester.makeChainFork(common+fork, fork, tester.genesis, nil, true) + chainA := testChainForkLightA.shorten(len(testChainBase.blocks) + MaxHeaderFetch) + chainB := testChainForkLightB.shorten(len(testChainBase.blocks) + MaxHeaderFetch) // Set a sync init hook to catch progress changes starting := make(chan struct{}) @@ -1459,15 +1416,12 @@ func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) { starting <- struct{}{} <-progress } - // Retrieve the sync progress and ensure they are zero (pristine sync) - if progress := tester.downloader.Progress(); progress.StartingBlock != 0 || progress.CurrentBlock != 0 || progress.HighestBlock != 0 { - t.Fatalf("Pristine progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, 0, 0, 0) - } + checkProgress(t, tester.downloader, "pristine", tomochain.SyncProgress{}) + // Synchronise with one of the forks and check progress - tester.newPeer("fork A", protocol, hashesA, headersA, blocksA, receiptsA) + tester.newPeer("fork A", protocol, chainA.blocks[1:]) pending := new(sync.WaitGroup) pending.Add(1) - go func() { defer pending.Done() if err := tester.sync("fork A", nil, mode); err != nil { @@ -1475,9 +1429,10 @@ func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) { } }() <-starting - if progress := tester.downloader.Progress(); progress.StartingBlock != 0 || progress.CurrentBlock != 0 || progress.HighestBlock != uint64(len(hashesA)-1) { - t.Fatalf("Initial progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, 0, 0, len(hashesA)-1) - } + + checkProgress(t, tester.downloader, "initial", tomochain.SyncProgress{ + HighestBlock: uint64(len(chainA.blocks) - 1), + }) progress <- struct{}{} pending.Wait() @@ -1485,9 +1440,8 @@ func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) { tester.downloader.syncStatsChainOrigin = tester.downloader.syncStatsChainHeight // Synchronise with the second fork and check progress resets - tester.newPeer("fork B", protocol, hashesB, headersB, blocksB, receiptsB) + tester.newPeer("fork B", protocol, chainB.blocks[1:]) pending.Add(1) - go func() { defer pending.Done() if err := tester.sync("fork B", nil, mode); err != nil { @@ -1495,16 +1449,20 @@ func testForkedSyncProgress(t *testing.T, protocol int, mode SyncMode) { } }() <-starting - if progress := tester.downloader.Progress(); progress.StartingBlock != uint64(common) || progress.CurrentBlock != uint64(len(hashesA)-1) || progress.HighestBlock != uint64(len(hashesB)-1) { - t.Fatalf("Forking progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, common, len(hashesA)-1, len(hashesB)-1) - } - progress <- struct{}{} - pending.Wait() + checkProgress(t, tester.downloader, "forking", tomochain.SyncProgress{ + StartingBlock: uint64(len(testChainBase.blocks)) - 1, + CurrentBlock: uint64(len(chainA.blocks) - 1), + HighestBlock: uint64(len(chainB.blocks) - 1), + }) // Check final progress after successful sync - if progress := tester.downloader.Progress(); progress.StartingBlock != uint64(common) || progress.CurrentBlock != uint64(len(hashesB)-1) || progress.HighestBlock != uint64(len(hashesB)-1) { - t.Fatalf("Final progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, common, len(hashesB)-1, len(hashesB)-1) - } + progress <- struct{}{} + pending.Wait() + checkProgress(t, tester.downloader, "final", tomochain.SyncProgress{ + StartingBlock: uint64(len(testChainBase.blocks)) - 1, + CurrentBlock: uint64(len(chainB.blocks) - 1), + HighestBlock: uint64(len(chainB.blocks) - 1), + }) } // Tests that if synchronisation is aborted due to some failure, then the progress @@ -1517,15 +1475,11 @@ func TestFailedSyncProgress64Full(t *testing.T) { testFailedSyncProgress(t, 64, func TestFailedSyncProgress64Fast(t *testing.T) { testFailedSyncProgress(t, 64, FastSync) } func TestFailedSyncProgress64Light(t *testing.T) { testFailedSyncProgress(t, 64, LightSync) } -func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() - // Create a small enough block chain to download - targetBlocks := blockCacheItems - 15 - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks, 0, tester.genesis, nil, false) + chain := testChainBase.shorten(blockCacheMaxItems - 15) // Set a sync init hook to catch progress changes starting := make(chan struct{}) @@ -1535,20 +1489,16 @@ func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) { starting <- struct{}{} <-progress } - // Retrieve the sync progress and ensure they are zero (pristine sync) - if progress := tester.downloader.Progress(); progress.StartingBlock != 0 || progress.CurrentBlock != 0 || progress.HighestBlock != 0 { - t.Fatalf("Pristine progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, 0, 0, 0) - } + checkProgress(t, tester.downloader, "pristine", tomochain.SyncProgress{}) + // Attempt a full sync with a faulty peer - tester.newPeer("faulty", protocol, hashes, headers, blocks, receipts) - missing := targetBlocks / 2 - delete(tester.peerHeaders["faulty"], hashes[missing]) - delete(tester.peerBlocks["faulty"], hashes[missing]) - delete(tester.peerReceipts["faulty"], hashes[missing]) + missing := len(chain.blocks)/2 - 1 + + faulter := tester.newPeer("faulty", protocol, chain.blocks[1:]) + faulter.withholdHeaders[chain.blocks[missing].Hash()] = struct{}{} pending := new(sync.WaitGroup) pending.Add(1) - go func() { defer pending.Done() if err := tester.sync("faulty", nil, mode); err == nil { @@ -1556,16 +1506,17 @@ func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) { } }() <-starting - if progress := tester.downloader.Progress(); progress.StartingBlock != 0 || progress.CurrentBlock != 0 || progress.HighestBlock != uint64(targetBlocks) { - t.Fatalf("Initial progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, 0, 0, targetBlocks) - } + checkProgress(t, tester.downloader, "initial", tomochain.SyncProgress{ + HighestBlock: uint64(len(chain.blocks) - 1), + }) progress <- struct{}{} pending.Wait() + afterFailedSync := tester.downloader.Progress() - // Synchronise with a good peer and check that the progress origin remind the same after a failure - tester.newPeer("valid", protocol, hashes, headers, blocks, receipts) + // Synchronise with a good peer and check that the progress origin remind the same + // after a failure + tester.newPeer("valid", protocol, chain.blocks[1:]) pending.Add(1) - go func() { defer pending.Done() if err := tester.sync("valid", nil, mode); err != nil { @@ -1573,16 +1524,15 @@ func testFailedSyncProgress(t *testing.T, protocol int, mode SyncMode) { } }() <-starting - if progress := tester.downloader.Progress(); progress.StartingBlock != 0 || progress.CurrentBlock > uint64(targetBlocks/2) || progress.HighestBlock != uint64(targetBlocks) { - t.Fatalf("Completing progress mismatch: have %v/%v/%v, want %v/0-%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, 0, targetBlocks/2, targetBlocks) - } - progress <- struct{}{} - pending.Wait() + checkProgress(t, tester.downloader, "completing", afterFailedSync) // Check final progress after successful sync - if progress := tester.downloader.Progress(); progress.StartingBlock > uint64(targetBlocks/2) || progress.CurrentBlock != uint64(targetBlocks) || progress.HighestBlock != uint64(targetBlocks) { - t.Fatalf("Final progress mismatch: have %v/%v/%v, want 0-%v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, targetBlocks/2, targetBlocks, targetBlocks) - } + progress <- struct{}{} + pending.Wait() + checkProgress(t, tester.downloader, "final", tomochain.SyncProgress{ + CurrentBlock: uint64(len(chain.blocks) - 1), + HighestBlock: uint64(len(chain.blocks) - 1), + }) } // Tests that if an attacker fakes a chain height, after the attack is detected, @@ -1594,39 +1544,29 @@ func TestFakedSyncProgress64Full(t *testing.T) { testFakedSyncProgress(t, 64, F func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, FastSync) } func TestFakedSyncProgress64Light(t *testing.T) { testFakedSyncProgress(t, 64, LightSync) } -func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - tester := newTester() +func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { + tester := newTester(t) defer tester.terminate() - // Create a small block chain - targetBlocks := blockCacheItems - 15 - hashes, headers, blocks, receipts := tester.makeChain(targetBlocks+3, 0, tester.genesis, nil, false) + chain := testChainBase.shorten(blockCacheMaxItems - 15) // Set a sync init hook to catch progress changes starting := make(chan struct{}) progress := make(chan struct{}) - tester.downloader.syncInitHook = func(origin, latest uint64) { starting <- struct{}{} <-progress } - // Retrieve the sync progress and ensure they are zero (pristine sync) - if progress := tester.downloader.Progress(); progress.StartingBlock != 0 || progress.CurrentBlock != 0 || progress.HighestBlock != 0 { - t.Fatalf("Pristine progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, 0, 0, 0) - } - // Create and sync with an attacker that promises a higher chain than available - tester.newPeer("attack", protocol, hashes, headers, blocks, receipts) - for i := 1; i < 3; i++ { - delete(tester.peerHeaders["attack"], hashes[i]) - delete(tester.peerBlocks["attack"], hashes[i]) - delete(tester.peerReceipts["attack"], hashes[i]) - } + checkProgress(t, tester.downloader, "pristine", tomochain.SyncProgress{}) + // Create and sync with an attacker that promises a higher chain than available. + attacker := tester.newPeer("attack", protocol, chain.blocks[1:]) + numMissing := 5 + for i := len(chain.blocks) - 2; i > len(chain.blocks)-numMissing; i-- { + attacker.withholdHeaders[chain.blocks[i].Hash()] = struct{}{} + } pending := new(sync.WaitGroup) pending.Add(1) - go func() { defer pending.Done() if err := tester.sync("attack", nil, mode); err == nil { @@ -1634,14 +1574,17 @@ func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) { } }() <-starting - if progress := tester.downloader.Progress(); progress.StartingBlock != 0 || progress.CurrentBlock != 0 || progress.HighestBlock != uint64(targetBlocks+3) { - t.Fatalf("Initial progress mismatch: have %v/%v/%v, want %v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, 0, 0, targetBlocks+3) - } + checkProgress(t, tester.downloader, "initial", tomochain.SyncProgress{ + HighestBlock: uint64(len(chain.blocks) - 1), + }) progress <- struct{}{} pending.Wait() + afterFailedSync := tester.downloader.Progress() - // Synchronise with a good peer and check that the progress height has been reduced to the true value - tester.newPeer("valid", protocol, hashes[3:], headers, blocks, receipts) + // Synchronise with a good peer and check that the progress height has been reduced to + // the true value. + validChain := chain.shorten(len(chain.blocks) - numMissing) + tester.newPeer("valid", protocol, validChain.blocks[1:]) pending.Add(1) go func() { @@ -1651,16 +1594,17 @@ func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) { } }() <-starting - if progress := tester.downloader.Progress(); progress.StartingBlock != 0 || progress.CurrentBlock > uint64(targetBlocks) || progress.HighestBlock != uint64(targetBlocks) { - t.Fatalf("Completing progress mismatch: have %v/%v/%v, want %v/0-%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, 0, targetBlocks, targetBlocks) - } + checkProgress(t, tester.downloader, "completing", tomochain.SyncProgress{ + CurrentBlock: afterFailedSync.CurrentBlock, + HighestBlock: uint64(len(validChain.blocks) - 1), + }) + // Check final progress after successful sync. progress <- struct{}{} pending.Wait() - - // Check final progress after successful sync - if progress := tester.downloader.Progress(); progress.StartingBlock > uint64(targetBlocks) || progress.CurrentBlock != uint64(targetBlocks) || progress.HighestBlock != uint64(targetBlocks) { - t.Fatalf("Final progress mismatch: have %v/%v/%v, want 0-%v/%v/%v", progress.StartingBlock, progress.CurrentBlock, progress.HighestBlock, targetBlocks, targetBlocks, targetBlocks) - } + checkProgress(t, tester.downloader, "final", tomochain.SyncProgress{ + CurrentBlock: uint64(len(validChain.blocks) - 1), + HighestBlock: uint64(len(validChain.blocks) - 1), + }) } // This test reproduces an issue where unexpected deliveries would @@ -1669,7 +1613,7 @@ func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) { // and not with the other tests, avoiding intermittent failures. func TestDeliverHeadersHang(t *testing.T) { testCases := []struct { - protocol int + protocol uint syncMode SyncMode }{ {62, FullSync}, @@ -1686,6 +1630,30 @@ func TestDeliverHeadersHang(t *testing.T) { } } +func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) { + master := newTester(t) + defer master.terminate() + + chain := testChainBase.shorten(15) + + for i := 0; i < 200; i++ { + tester := newTester(t) + tester.peerDb = master.peerDb + tester.newPeer("peer", protocol, chain.blocks[1:]) + + // Whenever the downloader requests headers, flood it with + // a lot of unrequested header deliveries. + tester.downloader.peers.peers["peer"].peer = &floodingTestPeer{ + peer: tester.downloader.peers.peers["peer"].peer, + tester: tester, + } + if err := tester.sync("peer", nil, mode); err != nil { + t.Errorf("test %d: sync failed: %v", i, err) + } + tester.terminate() + } +} + type floodingTestPeer struct { peer Peer tester *downloadTester @@ -1731,31 +1699,3 @@ func (ftp *floodingTestPeer) RequestHeadersByNumber(from uint64, count, skip int } return nil } - -func testDeliverHeadersHang(t *testing.T, protocol int, mode SyncMode) { - t.Parallel() - - master := newTester() - defer master.terminate() - - hashes, headers, blocks, receipts := master.makeChain(5, 0, master.genesis, nil, false) - for i := 0; i < 200; i++ { - tester := newTester() - tester.peerDb = master.peerDb - - tester.newPeer("peer", protocol, hashes, headers, blocks, receipts) - // Whenever the downloader requests headers, flood it with - // a lot of unrequested header deliveries. - tester.downloader.peers.peers["peer"].peer = &floodingTestPeer{ - peer: tester.downloader.peers.peers["peer"].peer, - tester: tester, - } - if err := tester.sync("peer", nil, mode); err != nil { - t.Errorf("test %d: sync failed: %v", i, err) - } - tester.terminate() - - // Flush all goroutines to prevent messing with subsequent tests - tester.downloader.peers.peers["peer"].peer.(*floodingTestPeer).pend.Wait() - } -} diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 0ed4e75faa..3dd9f81319 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -33,6 +33,7 @@ import ( ) var ( + blockCacheMaxItems = 8192 // Maximum number of blocks to cache before throttling the download blockCacheItems = 8192 // Maximum number of blocks to cache before throttling the download blockCacheMemory = 64 * 1024 * 1024 // Maximum amount of memory to use for block caching blockCacheSizeWeight = 0.1 // Multiplier to approximate the average block size based on past ones diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go new file mode 100644 index 0000000000..debdd56b2e --- /dev/null +++ b/eth/downloader/testchain_test.go @@ -0,0 +1,230 @@ +// Copyright 2018 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 . + +package downloader + +import ( + "fmt" + "math/big" + "sync" + "time" + + "github.com/tomochain/tomochain/common" + "github.com/tomochain/tomochain/consensus/ethash" + "github.com/tomochain/tomochain/core" + "github.com/tomochain/tomochain/core/rawdb" + "github.com/tomochain/tomochain/core/types" + "github.com/tomochain/tomochain/core/vm" + "github.com/tomochain/tomochain/crypto" + "github.com/tomochain/tomochain/params" +) + +// Test chain parameters. +var ( + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddress = crypto.PubkeyToAddress(testKey.PublicKey) + testDB = rawdb.NewMemoryDatabase() + + testGspec = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, + BaseFee: big.NewInt(params.InitialBaseFee), + } + testGenesis = testGspec.MustCommit(testDB) +) + +// The common prefix of all test chains: +var testChainBase *testChain + +// Different forks on top of the base chain: +var testChainForkLightA, testChainForkLightB, testChainForkHeavy *testChain + +var pregenerated bool + +func init() { + // Reduce some of the parameters to make the tester faster + fullMaxForkAncestry = 10000 + lightMaxForkAncestry = 10000 + blockCacheMaxItems = 1024 + fsHeaderSafetyNet = 256 + fsHeaderContCheck = 500 * time.Millisecond + + testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis) + + var forkLen = int(fullMaxForkAncestry + 50) + var wg sync.WaitGroup + + // Generate the test chains to seed the peers with + wg.Add(3) + go func() { testChainForkLightA = testChainBase.makeFork(forkLen, false, 1); wg.Done() }() + go func() { testChainForkLightB = testChainBase.makeFork(forkLen, false, 2); wg.Done() }() + go func() { testChainForkHeavy = testChainBase.makeFork(forkLen, true, 3); wg.Done() }() + wg.Wait() + + // Generate the test peers used by the tests to avoid overloading during testing. + // These seemingly random chains are used in various downloader tests. We're just + // pre-generating them here. + chains := []*testChain{ + testChainBase, + testChainForkLightA, + testChainForkLightB, + testChainForkHeavy, + testChainBase.shorten(1), + testChainBase.shorten(blockCacheMaxItems - 15), + testChainBase.shorten((blockCacheMaxItems - 15) / 2), + testChainBase.shorten(blockCacheMaxItems - 15 - 5), + testChainBase.shorten(MaxHeaderFetch), + testChainBase.shorten(800), + testChainBase.shorten(800 / 2), + testChainBase.shorten(800 / 3), + testChainBase.shorten(800 / 4), + testChainBase.shorten(800 / 5), + testChainBase.shorten(800 / 6), + testChainBase.shorten(800 / 7), + testChainBase.shorten(800 / 8), + testChainBase.shorten(3*fsHeaderSafetyNet + 256 + fsMinFullBlocks), + testChainBase.shorten(fsMinFullBlocks + 256 - 1), + testChainForkLightA.shorten(len(testChainBase.blocks) + 80), + testChainForkLightB.shorten(len(testChainBase.blocks) + 81), + testChainForkLightA.shorten(len(testChainBase.blocks) + MaxHeaderFetch), + testChainForkLightB.shorten(len(testChainBase.blocks) + MaxHeaderFetch), + testChainForkHeavy.shorten(len(testChainBase.blocks) + 79), + } + wg.Add(len(chains)) + for _, chain := range chains { + go func(blocks []*types.Block) { + newTestBlockchain(blocks) + wg.Done() + }(chain.blocks[1:]) + } + wg.Wait() + + // Mark the chains pregenerated. Generating a new one will lead to a panic. + pregenerated = true +} + +type testChain struct { + blocks []*types.Block +} + +// newTestChain creates a blockchain of the given length. +func newTestChain(length int, genesis *types.Block) *testChain { + tc := &testChain{ + blocks: []*types.Block{genesis}, + } + tc.generate(length-1, 0, genesis, false) + return tc +} + +// makeFork creates a fork on top of the test chain. +func (tc *testChain) makeFork(length int, heavy bool, seed byte) *testChain { + fork := tc.copy(len(tc.blocks) + length) + fork.generate(length, seed, tc.blocks[len(tc.blocks)-1], heavy) + return fork +} + +// shorten creates a copy of the chain with the given length. It panics if the +// length is longer than the number of available blocks. +func (tc *testChain) shorten(length int) *testChain { + if length > len(tc.blocks) { + panic(fmt.Errorf("can't shorten test chain to %d blocks, it's only %d blocks long", length, len(tc.blocks))) + } + return tc.copy(length) +} + +func (tc *testChain) copy(newlen int) *testChain { + if newlen > len(tc.blocks) { + newlen = len(tc.blocks) + } + cpy := &testChain{ + blocks: append([]*types.Block{}, tc.blocks[:newlen]...), + } + return cpy +} + +// generate creates a chain of n blocks starting at and including parent. +// the returned hash chain is ordered head->parent. In addition, every 22th block +// contains a transaction and every 5th an uncle to allow testing correct block +// reassembly. +func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) { + blocks, _ := core.GenerateChain(testGspec.Config, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) { + block.SetCoinbase(common.Address{seed}) + // If a heavy chain is requested, delay blocks to raise difficulty + if heavy { + block.OffsetTime(-9) + } + // Include transactions to the miner to make blocks more interesting. + if parent == tc.blocks[0] && i%22 == 0 { + signer := types.MakeSigner(params.TestChainConfig, block.Number()) + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) + if err != nil { + panic(err) + } + block.AddTx(tx) + } + // if the block number is a multiple of 5, add a bonus uncle to the block + if i > 0 && i%5 == 0 { + block.AddUncle(&types.Header{ + ParentHash: block.PrevBlock(i - 2).Hash(), + Number: big.NewInt(block.Number().Int64() - 1), + }) + } + }) + tc.blocks = append(tc.blocks, blocks...) +} + +var ( + testBlockchains = make(map[common.Hash]*testBlockchain) + testBlockchainsLock sync.Mutex +) + +type testBlockchain struct { + chain *core.BlockChain + gen sync.Once +} + +// newTestBlockchain creates a blockchain database built by running the given blocks, +// either actually running them, or reusing a previously created one. The returned +// chains are *shared*, so *do not* mutate them. +func newTestBlockchain(blocks []*types.Block) *core.BlockChain { + // Retrieve an existing database, or create a new one + head := testGenesis.Hash() + if len(blocks) > 0 { + head = blocks[len(blocks)-1].Hash() + } + testBlockchainsLock.Lock() + if _, ok := testBlockchains[head]; !ok { + testBlockchains[head] = new(testBlockchain) + } + tbc := testBlockchains[head] + testBlockchainsLock.Unlock() + + // Ensure that the database is generated + tbc.gen.Do(func() { + if pregenerated { + panic("Requested chain generation outside of init") + } + chain, err := core.NewBlockChain(testDB, nil, testGspec.Config, ethash.NewFaker(), vm.Config{}) + if err != nil { + panic(err) + } + if n, err := chain.InsertChain(blocks); err != nil { + panic(fmt.Sprintf("block %d: %v", n, err)) + } + tbc.chain = chain + }) + return tbc.chain +} diff --git a/params/network_params.go b/params/network_params.go index 536a46d3df..6a0f4a2248 100644 --- a/params/network_params.go +++ b/params/network_params.go @@ -23,4 +23,16 @@ const ( // BloomBitsBlocks is the number of blocks a single bloom bit section vector // contains. BloomBitsBlocks uint64 = 4096 + + // FullImmutabilityThreshold is the number of blocks after which a chain segment is + // considered immutable (i.e. soft finality). It is used by the downloader as a + // hard limit against deep ancestors, by the blockchain against deep reorgs, by + // the freezer as the cutoff threshold and by clique as the snapshot trust limit. + FullImmutabilityThreshold = 90000 + + // LightImmutabilityThreshold is the number of blocks after which a header chain + // segment is considered immutable for light client(i.e. soft finality). It is used by + // the downloader as a hard limit against deep ancestors, by the blockchain against deep + // reorgs, by the light pruner as the pruning validity guarantee. + LightImmutabilityThreshold = 30000 )