Merge tag 'v1.10.14' into develop
This commit is contained in:
@@ -17,14 +17,21 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
@@ -76,6 +83,172 @@ func TestHeaderVerification(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderVerificationForMergingClique(t *testing.T) { testHeaderVerificationForMerging(t, true) }
|
||||
func TestHeaderVerificationForMergingEthash(t *testing.T) { testHeaderVerificationForMerging(t, false) }
|
||||
|
||||
// Tests the verification for eth1/2 merging, including pre-merge and post-merge
|
||||
func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
|
||||
var (
|
||||
testdb = rawdb.NewMemoryDatabase()
|
||||
preBlocks []*types.Block
|
||||
postBlocks []*types.Block
|
||||
runEngine consensus.Engine
|
||||
chainConfig *params.ChainConfig
|
||||
merger = consensus.NewMerger(rawdb.NewMemoryDatabase())
|
||||
)
|
||||
if isClique {
|
||||
var (
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||
engine = clique.New(params.AllCliqueProtocolChanges.Clique, testdb)
|
||||
)
|
||||
genspec := &Genesis{
|
||||
ExtraData: make([]byte, 32+common.AddressLength+crypto.SignatureLength),
|
||||
Alloc: map[common.Address]GenesisAccount{
|
||||
addr: {Balance: big.NewInt(1)},
|
||||
},
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
copy(genspec.ExtraData[32:], addr[:])
|
||||
genesis := genspec.MustCommit(testdb)
|
||||
|
||||
genEngine := beacon.New(engine)
|
||||
preBlocks, _ = GenerateChain(params.AllCliqueProtocolChanges, genesis, genEngine, testdb, 8, nil)
|
||||
td := 0
|
||||
for i, block := range preBlocks {
|
||||
header := block.Header()
|
||||
if i > 0 {
|
||||
header.ParentHash = preBlocks[i-1].Hash()
|
||||
}
|
||||
header.Extra = make([]byte, 32+crypto.SignatureLength)
|
||||
header.Difficulty = big.NewInt(2)
|
||||
|
||||
sig, _ := crypto.Sign(genEngine.SealHash(header).Bytes(), key)
|
||||
copy(header.Extra[len(header.Extra)-crypto.SignatureLength:], sig)
|
||||
preBlocks[i] = block.WithSeal(header)
|
||||
// calculate td
|
||||
td += int(block.Difficulty().Uint64())
|
||||
}
|
||||
config := *params.AllCliqueProtocolChanges
|
||||
config.TerminalTotalDifficulty = big.NewInt(int64(td))
|
||||
postBlocks, _ = GenerateChain(&config, preBlocks[len(preBlocks)-1], genEngine, testdb, 8, nil)
|
||||
chainConfig = &config
|
||||
runEngine = beacon.New(engine)
|
||||
} else {
|
||||
gspec := &Genesis{Config: params.TestChainConfig}
|
||||
genesis := gspec.MustCommit(testdb)
|
||||
genEngine := beacon.New(ethash.NewFaker())
|
||||
|
||||
preBlocks, _ = GenerateChain(params.TestChainConfig, genesis, genEngine, testdb, 8, nil)
|
||||
td := 0
|
||||
for _, block := range preBlocks {
|
||||
// calculate td
|
||||
td += int(block.Difficulty().Uint64())
|
||||
}
|
||||
config := *params.TestChainConfig
|
||||
config.TerminalTotalDifficulty = big.NewInt(int64(td))
|
||||
postBlocks, _ = GenerateChain(params.TestChainConfig, preBlocks[len(preBlocks)-1], genEngine, testdb, 8, nil)
|
||||
|
||||
chainConfig = &config
|
||||
runEngine = beacon.New(ethash.NewFaker())
|
||||
}
|
||||
|
||||
preHeaders := make([]*types.Header, len(preBlocks))
|
||||
for i, block := range preBlocks {
|
||||
preHeaders[i] = block.Header()
|
||||
|
||||
blob, _ := json.Marshal(block.Header())
|
||||
t.Logf("Log header before the merging %d: %v", block.NumberU64(), string(blob))
|
||||
}
|
||||
postHeaders := make([]*types.Header, len(postBlocks))
|
||||
for i, block := range postBlocks {
|
||||
postHeaders[i] = block.Header()
|
||||
|
||||
blob, _ := json.Marshal(block.Header())
|
||||
t.Logf("Log header after the merging %d: %v", block.NumberU64(), string(blob))
|
||||
}
|
||||
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
|
||||
chain, _ := NewBlockChain(testdb, nil, chainConfig, runEngine, vm.Config{}, nil, nil)
|
||||
defer chain.Stop()
|
||||
|
||||
// Verify the blocks before the merging
|
||||
for i := 0; i < len(preBlocks); i++ {
|
||||
_, results := runEngine.VerifyHeaders(chain, []*types.Header{preHeaders[i]}, []bool{true})
|
||||
// Wait for the verification result
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
t.Errorf("test %d: verification failed %v", i, result)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("test %d: verification timeout", i)
|
||||
}
|
||||
// Make sure no more data is returned
|
||||
select {
|
||||
case result := <-results:
|
||||
t.Fatalf("test %d: unexpected result returned: %v", i, result)
|
||||
case <-time.After(25 * time.Millisecond):
|
||||
}
|
||||
chain.InsertChain(preBlocks[i : i+1])
|
||||
}
|
||||
|
||||
// Make the transition
|
||||
merger.ReachTTD()
|
||||
merger.FinalizePoS()
|
||||
|
||||
// Verify the blocks after the merging
|
||||
for i := 0; i < len(postBlocks); i++ {
|
||||
_, results := runEngine.VerifyHeaders(chain, []*types.Header{postHeaders[i]}, []bool{true})
|
||||
// Wait for the verification result
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
t.Errorf("test %d: verification failed %v", i, result)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("test %d: verification timeout", i)
|
||||
}
|
||||
// Make sure no more data is returned
|
||||
select {
|
||||
case result := <-results:
|
||||
t.Fatalf("test %d: unexpected result returned: %v", i, result)
|
||||
case <-time.After(25 * time.Millisecond):
|
||||
}
|
||||
chain.InsertBlockWithoutSetHead(postBlocks[i])
|
||||
}
|
||||
|
||||
// Verify the blocks with pre-merge blocks and post-merge blocks
|
||||
var (
|
||||
headers []*types.Header
|
||||
seals []bool
|
||||
)
|
||||
for _, block := range preBlocks {
|
||||
headers = append(headers, block.Header())
|
||||
seals = append(seals, true)
|
||||
}
|
||||
for _, block := range postBlocks {
|
||||
headers = append(headers, block.Header())
|
||||
seals = append(seals, true)
|
||||
}
|
||||
_, results := runEngine.VerifyHeaders(chain, headers, seals)
|
||||
for i := 0; i < len(headers); i++ {
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
t.Errorf("test %d: verification failed %v", i, result)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("test %d: verification timeout", i)
|
||||
}
|
||||
}
|
||||
// Make sure no more data is returned
|
||||
select {
|
||||
case result := <-results:
|
||||
t.Fatalf("unexpected result returned: %v", result)
|
||||
case <-time.After(25 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that concurrent header verification works, for both good and bad blocks.
|
||||
func TestHeaderConcurrentVerification2(t *testing.T) { testHeaderConcurrentVerification(t, 2) }
|
||||
func TestHeaderConcurrentVerification8(t *testing.T) { testHeaderConcurrentVerification(t, 8) }
|
||||
|
||||
+289
-178
@@ -22,7 +22,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -208,15 +207,14 @@ type BlockChain struct {
|
||||
validator Validator // Block and state validator interface
|
||||
prefetcher Prefetcher
|
||||
processor Processor // Block transaction processor interface
|
||||
forker *ForkChoice
|
||||
vmConfig vm.Config
|
||||
|
||||
shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
|
||||
}
|
||||
|
||||
// NewBlockChain returns a fully initialised block chain using information
|
||||
// available in the database. It initialises the default Ethereum Validator and
|
||||
// Processor.
|
||||
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool, txLookupLimit *uint64) (*BlockChain, error) {
|
||||
// available in the database. It initialises the default Ethereum Validator
|
||||
// and Processor.
|
||||
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64) (*BlockChain, error) {
|
||||
if cacheConfig == nil {
|
||||
cacheConfig = defaultCacheConfig
|
||||
}
|
||||
@@ -237,18 +235,18 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
Journal: cacheConfig.TrieCleanJournal,
|
||||
Preimages: cacheConfig.Preimages,
|
||||
}),
|
||||
quit: make(chan struct{}),
|
||||
chainmu: syncx.NewClosableMutex(),
|
||||
shouldPreserve: shouldPreserve,
|
||||
bodyCache: bodyCache,
|
||||
bodyRLPCache: bodyRLPCache,
|
||||
receiptsCache: receiptsCache,
|
||||
blockCache: blockCache,
|
||||
txLookupCache: txLookupCache,
|
||||
futureBlocks: futureBlocks,
|
||||
engine: engine,
|
||||
vmConfig: vmConfig,
|
||||
quit: make(chan struct{}),
|
||||
chainmu: syncx.NewClosableMutex(),
|
||||
bodyCache: bodyCache,
|
||||
bodyRLPCache: bodyRLPCache,
|
||||
receiptsCache: receiptsCache,
|
||||
blockCache: blockCache,
|
||||
txLookupCache: txLookupCache,
|
||||
futureBlocks: futureBlocks,
|
||||
engine: engine,
|
||||
vmConfig: vmConfig,
|
||||
}
|
||||
bc.forker = NewForkChoice(bc, shouldPreserve)
|
||||
bc.validator = NewBlockValidator(chainConfig, bc, engine)
|
||||
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
|
||||
bc.processor = NewStateProcessor(chainConfig, bc, engine)
|
||||
@@ -382,7 +380,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||
|
||||
// Start future block processor.
|
||||
bc.wg.Add(1)
|
||||
go bc.futureBlocksLoop()
|
||||
go bc.updateFutureBlocks()
|
||||
|
||||
// Start tx indexer/unindexer.
|
||||
if txLookupLimit != nil {
|
||||
@@ -631,9 +629,9 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, root common.Hash, repair bo
|
||||
return rootNumber, bc.loadLastState()
|
||||
}
|
||||
|
||||
// FastSyncCommitHead sets the current head block to the one defined by the hash
|
||||
// SnapSyncCommitHead sets the current head block to the one defined by the hash
|
||||
// irrelevant what the chain contents were prior.
|
||||
func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {
|
||||
func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error {
|
||||
// Make sure that both the block as well at its state trie exists
|
||||
block := bc.GetBlockByHash(hash)
|
||||
if block == nil {
|
||||
@@ -738,30 +736,24 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
|
||||
//
|
||||
// Note, this function assumes that the `mu` mutex is held!
|
||||
func (bc *BlockChain) writeHeadBlock(block *types.Block) {
|
||||
// If the block is on a side chain or an unknown one, force other heads onto it too
|
||||
updateHeads := rawdb.ReadCanonicalHash(bc.db, block.NumberU64()) != block.Hash()
|
||||
|
||||
// Add the block to the canonical chain number scheme and mark as the head
|
||||
batch := bc.db.NewBatch()
|
||||
rawdb.WriteHeadHeaderHash(batch, block.Hash())
|
||||
rawdb.WriteHeadFastBlockHash(batch, block.Hash())
|
||||
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
|
||||
rawdb.WriteTxLookupEntriesByBlock(batch, block)
|
||||
rawdb.WriteHeadBlockHash(batch, block.Hash())
|
||||
|
||||
// If the block is better than our head or is on a different chain, force update heads
|
||||
if updateHeads {
|
||||
rawdb.WriteHeadHeaderHash(batch, block.Hash())
|
||||
rawdb.WriteHeadFastBlockHash(batch, block.Hash())
|
||||
}
|
||||
// Flush the whole batch into the disk, exit the node if failed
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to update chain indexes and markers", "err", err)
|
||||
}
|
||||
// Update all in-memory chain markers in the last step
|
||||
if updateHeads {
|
||||
bc.hc.SetCurrentHeader(block.Header())
|
||||
bc.currentFastBlock.Store(block)
|
||||
headFastBlockGauge.Update(int64(block.NumberU64()))
|
||||
}
|
||||
bc.hc.SetCurrentHeader(block.Header())
|
||||
|
||||
bc.currentFastBlock.Store(block)
|
||||
headFastBlockGauge.Update(int64(block.NumberU64()))
|
||||
|
||||
bc.currentBlock.Store(block)
|
||||
headBlockGauge.Update(int64(block.NumberU64()))
|
||||
}
|
||||
@@ -877,12 +869,6 @@ const (
|
||||
SideStatTy
|
||||
)
|
||||
|
||||
// numberHash is just a container for a number and a hash, to represent a block
|
||||
type numberHash struct {
|
||||
number uint64
|
||||
hash common.Hash
|
||||
}
|
||||
|
||||
// InsertReceiptChain attempts to complete an already existing header chain with
|
||||
// transaction and receipt data.
|
||||
func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts, ancientLimit uint64) (int, error) {
|
||||
@@ -928,13 +914,17 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
|
||||
|
||||
// Rewind may have occurred, skip in that case.
|
||||
if bc.CurrentHeader().Number.Cmp(head.Number()) >= 0 {
|
||||
currentFastBlock, td := bc.CurrentFastBlock(), bc.GetTd(head.Hash(), head.NumberU64())
|
||||
if bc.GetTd(currentFastBlock.Hash(), currentFastBlock.NumberU64()).Cmp(td) < 0 {
|
||||
rawdb.WriteHeadFastBlockHash(bc.db, head.Hash())
|
||||
bc.currentFastBlock.Store(head)
|
||||
headFastBlockGauge.Update(int64(head.NumberU64()))
|
||||
return true
|
||||
reorg, err := bc.forker.ReorgNeeded(bc.CurrentFastBlock().Header(), head.Header())
|
||||
if err != nil {
|
||||
log.Warn("Reorg failed", "err", err)
|
||||
return false
|
||||
} else if !reorg {
|
||||
return false
|
||||
}
|
||||
rawdb.WriteHeadFastBlockHash(bc.db, head.Hash())
|
||||
bc.currentFastBlock.Store(head)
|
||||
headFastBlockGauge.Update(int64(head.NumberU64()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1181,30 +1171,15 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteBlockWithState writes the block and all associated state to the database.
|
||||
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
|
||||
if !bc.chainmu.TryLock() {
|
||||
return NonStatTy, errInsertionInterrupted
|
||||
}
|
||||
defer bc.chainmu.Unlock()
|
||||
return bc.writeBlockWithState(block, receipts, logs, state, emitHeadEvent)
|
||||
}
|
||||
|
||||
// writeBlockWithState writes the block and all associated state to the database,
|
||||
// but is expects the chain mutex to be held.
|
||||
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
|
||||
if bc.insertStopped() {
|
||||
return NonStatTy, errInsertionInterrupted
|
||||
}
|
||||
|
||||
// writeBlockWithState writes block, metadata and corresponding state data to the
|
||||
// database.
|
||||
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB) error {
|
||||
// Calculate the total difficulty of the block
|
||||
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
|
||||
if ptd == nil {
|
||||
return NonStatTy, consensus.ErrUnknownAncestor
|
||||
return consensus.ErrUnknownAncestor
|
||||
}
|
||||
// Make sure no inconsistent state is leaked during insertion
|
||||
currentBlock := bc.CurrentBlock()
|
||||
localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
|
||||
externTd := new(big.Int).Add(block.Difficulty(), ptd)
|
||||
|
||||
// Irrelevant of the canonical status, write the block itself to the database.
|
||||
@@ -1222,15 +1197,13 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
// Commit all cached state changes into underlying memory database.
|
||||
root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number()))
|
||||
if err != nil {
|
||||
return NonStatTy, err
|
||||
return err
|
||||
}
|
||||
triedb := bc.stateCache.TrieDB()
|
||||
|
||||
// If we're running an archive node, always flush
|
||||
if bc.cacheConfig.TrieDirtyDisabled {
|
||||
if err := triedb.Commit(root, false, nil); err != nil {
|
||||
return NonStatTy, err
|
||||
}
|
||||
return triedb.Commit(root, false, nil)
|
||||
} else {
|
||||
// Full but not archive node, do proper garbage collection
|
||||
triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive
|
||||
@@ -1278,23 +1251,30 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the total difficulty is higher than our known, add it to the canonical chain
|
||||
// Second clause in the if statement reduces the vulnerability to selfish mining.
|
||||
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
|
||||
reorg := externTd.Cmp(localTd) > 0
|
||||
currentBlock = bc.CurrentBlock()
|
||||
if !reorg && externTd.Cmp(localTd) == 0 {
|
||||
// Split same-difficulty blocks by number, then preferentially select
|
||||
// the block generated by the local miner as the canonical block.
|
||||
if block.NumberU64() < currentBlock.NumberU64() {
|
||||
reorg = true
|
||||
} else if block.NumberU64() == currentBlock.NumberU64() {
|
||||
var currentPreserve, blockPreserve bool
|
||||
if bc.shouldPreserve != nil {
|
||||
currentPreserve, blockPreserve = bc.shouldPreserve(currentBlock), bc.shouldPreserve(block)
|
||||
}
|
||||
reorg = !currentPreserve && (blockPreserve || mrand.Float64() < 0.5)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteBlockWithState writes the block and all associated state to the database.
|
||||
func (bc *BlockChain) WriteBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
|
||||
if !bc.chainmu.TryLock() {
|
||||
return NonStatTy, errChainStopped
|
||||
}
|
||||
defer bc.chainmu.Unlock()
|
||||
|
||||
return bc.writeBlockAndSetHead(block, receipts, logs, state, emitHeadEvent)
|
||||
}
|
||||
|
||||
// writeBlockAndSetHead writes the block and all associated state to the database,
|
||||
// and also it applies the given block as the new chain head. This function expects
|
||||
// the chain mutex to be held.
|
||||
func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
|
||||
if err := bc.writeBlockWithState(block, receipts, logs, state); err != nil {
|
||||
return NonStatTy, err
|
||||
}
|
||||
currentBlock := bc.CurrentBlock()
|
||||
reorg, err := bc.forker.ReorgNeeded(currentBlock.Header(), block.Header())
|
||||
if err != nil {
|
||||
return NonStatTy, err
|
||||
}
|
||||
if reorg {
|
||||
// Reorganise the chain if the parent is not the head block
|
||||
@@ -1321,7 +1301,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
}
|
||||
// In theory we should fire a ChainHeadEvent when we inject
|
||||
// a canonical block, but sometimes we can insert a batch of
|
||||
// canonicial blocks. Avoid firing too much ChainHeadEvents,
|
||||
// canonicial blocks. Avoid firing too many ChainHeadEvents,
|
||||
// we will fire an accumulated ChainHeadEvent and disable fire
|
||||
// event here.
|
||||
if emitHeadEvent {
|
||||
@@ -1337,11 +1317,18 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
|
||||
// addFutureBlock checks if the block is within the max allowed window to get
|
||||
// accepted for future processing, and returns an error if the block is too far
|
||||
// ahead and was not added.
|
||||
//
|
||||
// TODO after the transition, the future block shouldn't be kept. Because
|
||||
// it's not checked in the Geth side anymore.
|
||||
func (bc *BlockChain) addFutureBlock(block *types.Block) error {
|
||||
max := uint64(time.Now().Unix() + maxTimeFutureBlocks)
|
||||
if block.Time() > max {
|
||||
return fmt.Errorf("future block timestamp %v > allowed %v", block.Time(), max)
|
||||
}
|
||||
if block.Difficulty().Cmp(common.Big0) == 0 {
|
||||
// Never add PoS blocks into the future queue
|
||||
return nil
|
||||
}
|
||||
bc.futureBlocks.Add(block.Hash(), block)
|
||||
return nil
|
||||
}
|
||||
@@ -1349,15 +1336,12 @@ func (bc *BlockChain) addFutureBlock(block *types.Block) error {
|
||||
// InsertChain attempts to insert the given batch of blocks in to the canonical
|
||||
// chain or, otherwise, create a fork. If an error is returned it will return
|
||||
// the index number of the failing block as well an error describing what went
|
||||
// wrong.
|
||||
//
|
||||
// After insertion is done, all accumulated events will be fired.
|
||||
// wrong. After insertion is done, all accumulated events will be fired.
|
||||
func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
|
||||
// Sanity check that we have something meaningful to import
|
||||
if len(chain) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
bc.blockProcFeed.Send(true)
|
||||
defer bc.blockProcFeed.Send(false)
|
||||
|
||||
@@ -1376,26 +1360,12 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
|
||||
prev.Hash().Bytes()[:4], i, block.NumberU64(), block.Hash().Bytes()[:4], block.ParentHash().Bytes()[:4])
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-check passed, start the full block imports.
|
||||
// Pre-checks passed, start the full block imports
|
||||
if !bc.chainmu.TryLock() {
|
||||
return 0, errChainStopped
|
||||
}
|
||||
defer bc.chainmu.Unlock()
|
||||
return bc.insertChain(chain, true)
|
||||
}
|
||||
|
||||
// InsertChainWithoutSealVerification works exactly the same
|
||||
// except for seal verification, seal verification is omitted
|
||||
func (bc *BlockChain) InsertChainWithoutSealVerification(block *types.Block) (int, error) {
|
||||
bc.blockProcFeed.Send(true)
|
||||
defer bc.blockProcFeed.Send(false)
|
||||
|
||||
if !bc.chainmu.TryLock() {
|
||||
return 0, errChainStopped
|
||||
}
|
||||
defer bc.chainmu.Unlock()
|
||||
return bc.insertChain(types.Blocks([]*types.Block{block}), false)
|
||||
return bc.insertChain(chain, true, true)
|
||||
}
|
||||
|
||||
// insertChain is the internal implementation of InsertChain, which assumes that
|
||||
@@ -1406,7 +1376,7 @@ func (bc *BlockChain) InsertChainWithoutSealVerification(block *types.Block) (in
|
||||
// racey behaviour. If a sidechain import is in progress, and the historic state
|
||||
// is imported, but then new canon-head is added before the actual sidechain
|
||||
// completes, then the historic state could be pruned again
|
||||
func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, error) {
|
||||
func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool) (int, error) {
|
||||
// If the chain is terminating, don't even bother starting up.
|
||||
if bc.insertStopped() {
|
||||
return 0, nil
|
||||
@@ -1448,14 +1418,23 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
|
||||
// from the canonical chain, which has not been verified.
|
||||
// Skip all known blocks that are behind us.
|
||||
var (
|
||||
current = bc.CurrentBlock()
|
||||
localTd = bc.GetTd(current.Hash(), current.NumberU64())
|
||||
externTd = bc.GetTd(block.ParentHash(), block.NumberU64()-1) // The first block can't be nil
|
||||
reorg bool
|
||||
current = bc.CurrentBlock()
|
||||
)
|
||||
for block != nil && bc.skipBlock(err, it) {
|
||||
externTd = new(big.Int).Add(externTd, block.Difficulty())
|
||||
if localTd.Cmp(externTd) < 0 {
|
||||
break
|
||||
reorg, err = bc.forker.ReorgNeeded(current.Header(), block.Header())
|
||||
if err != nil {
|
||||
return it.index, err
|
||||
}
|
||||
if reorg {
|
||||
// Switch to import mode if the forker says the reorg is necessary
|
||||
// and also the block is not on the canonical chain.
|
||||
// In eth2 the forker always returns true for reorg decision (blindly trusting
|
||||
// the external consensus engine), but in order to prevent the unnecessary
|
||||
// reorgs when importing known blocks, the special case is handled here.
|
||||
if block.NumberU64() > current.NumberU64() || bc.GetCanonicalHash(block.NumberU64()) != block.Hash() {
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Debug("Ignoring already known block", "number", block.Number(), "hash", block.Hash())
|
||||
stats.ignored++
|
||||
@@ -1482,11 +1461,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
|
||||
// Falls through to the block import
|
||||
}
|
||||
switch {
|
||||
// First block is pruned, insert as sidechain and reorg only if TD grows enough
|
||||
// First block is pruned
|
||||
case errors.Is(err, consensus.ErrPrunedAncestor):
|
||||
log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash())
|
||||
return bc.insertSideChain(block, it)
|
||||
|
||||
if setHead {
|
||||
// First block is pruned, insert as sidechain and reorg only if TD grows enough
|
||||
log.Debug("Pruned ancestor, inserting as sidechain", "number", block.Number(), "hash", block.Hash())
|
||||
return bc.insertSideChain(block, it)
|
||||
} else {
|
||||
// We're post-merge and the parent is pruned, try to recover the parent state
|
||||
log.Debug("Pruned ancestor", "number", block.Number(), "hash", block.Hash())
|
||||
return it.index, bc.recoverAncestors(block)
|
||||
}
|
||||
// First block is future, shove it (and all children) to the future queue (unknown ancestor)
|
||||
case errors.Is(err, consensus.ErrFutureBlock) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())):
|
||||
for block != nil && (it.index == 0 || errors.Is(err, consensus.ErrUnknownAncestor)) {
|
||||
@@ -1641,12 +1626,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
|
||||
// Update the metrics touched during block validation
|
||||
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete, we can mark them
|
||||
storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete, we can mark them
|
||||
|
||||
blockValidationTimer.Update(time.Since(substart) - (statedb.AccountHashes + statedb.StorageHashes - triehash))
|
||||
|
||||
// Write the block to the chain and get the status.
|
||||
substart = time.Now()
|
||||
status, err := bc.writeBlockWithState(block, receipts, logs, statedb, false)
|
||||
var status WriteStatus
|
||||
if !setHead {
|
||||
// Don't set the head, only insert the block
|
||||
err = bc.writeBlockWithState(block, receipts, logs, statedb)
|
||||
} else {
|
||||
status, err = bc.writeBlockAndSetHead(block, receipts, logs, statedb, false)
|
||||
}
|
||||
atomic.StoreUint32(&followupInterrupt, 1)
|
||||
if err != nil {
|
||||
return it.index, err
|
||||
@@ -1659,6 +1649,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
|
||||
blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits)
|
||||
blockInsertTimer.UpdateSince(start)
|
||||
|
||||
if !setHead {
|
||||
// We did not setHead, so we don't have any stats to update
|
||||
log.Info("Inserted block", "number", block.Number(), "hash", block.Hash(), "txs", len(block.Transactions()), "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
return it.index, nil
|
||||
}
|
||||
|
||||
switch status {
|
||||
case CanonStatTy:
|
||||
log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(),
|
||||
@@ -1717,10 +1713,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
|
||||
//
|
||||
// The method writes all (header-and-body-valid) blocks to disk, then tries to
|
||||
// switch over to the new chain if the TD exceeded the current chain.
|
||||
// insertSideChain is only used pre-merge.
|
||||
func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (int, error) {
|
||||
var (
|
||||
externTd *big.Int
|
||||
current = bc.CurrentBlock()
|
||||
externTd *big.Int
|
||||
lastBlock = block
|
||||
current = bc.CurrentBlock()
|
||||
)
|
||||
// The first sidechain block error is already verified to be ErrPrunedAncestor.
|
||||
// Since we don't import them here, we expect ErrUnknownAncestor for the remaining
|
||||
@@ -1771,6 +1769,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||
"txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()),
|
||||
"root", block.Root())
|
||||
}
|
||||
lastBlock = block
|
||||
}
|
||||
// At this point, we've written all sidechain blocks to database. Loop ended
|
||||
// either on some other error or all were processed. If there was some other
|
||||
@@ -1778,8 +1777,12 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||
//
|
||||
// If the externTd was larger than our local TD, we now need to reimport the previous
|
||||
// blocks to regenerate the required state
|
||||
localTd := bc.GetTd(current.Hash(), current.NumberU64())
|
||||
if localTd.Cmp(externTd) > 0 {
|
||||
reorg, err := bc.forker.ReorgNeeded(current.Header(), lastBlock.Header())
|
||||
if err != nil {
|
||||
return it.index, err
|
||||
}
|
||||
if !reorg {
|
||||
localTd := bc.GetTd(current.Hash(), current.NumberU64())
|
||||
log.Info("Sidechain written to disk", "start", it.first().NumberU64(), "end", it.previous().Number, "sidetd", externTd, "localtd", localTd)
|
||||
return it.index, err
|
||||
}
|
||||
@@ -1815,7 +1818,7 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||
// memory here.
|
||||
if len(blocks) >= 2048 || memory > 64*1024*1024 {
|
||||
log.Info("Importing heavy sidechain segment", "blocks", len(blocks), "start", blocks[0].NumberU64(), "end", block.NumberU64())
|
||||
if _, err := bc.insertChain(blocks, false); err != nil {
|
||||
if _, err := bc.insertChain(blocks, false, true); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
blocks, memory = blocks[:0], 0
|
||||
@@ -1829,14 +1832,98 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i
|
||||
}
|
||||
if len(blocks) > 0 {
|
||||
log.Info("Importing sidechain segment", "start", blocks[0].NumberU64(), "end", blocks[len(blocks)-1].NumberU64())
|
||||
return bc.insertChain(blocks, false)
|
||||
return bc.insertChain(blocks, false, true)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// recoverAncestors finds the closest ancestor with available state and re-execute
|
||||
// all the ancestor blocks since that.
|
||||
// recoverAncestors is only used post-merge.
|
||||
func (bc *BlockChain) recoverAncestors(block *types.Block) error {
|
||||
// Gather all the sidechain hashes (full blocks may be memory heavy)
|
||||
var (
|
||||
hashes []common.Hash
|
||||
numbers []uint64
|
||||
parent = block
|
||||
)
|
||||
for parent != nil && !bc.HasState(parent.Root()) {
|
||||
hashes = append(hashes, parent.Hash())
|
||||
numbers = append(numbers, parent.NumberU64())
|
||||
parent = bc.GetBlock(parent.ParentHash(), parent.NumberU64()-1)
|
||||
|
||||
// If the chain is terminating, stop iteration
|
||||
if bc.insertStopped() {
|
||||
log.Debug("Abort during blocks iteration")
|
||||
return errInsertionInterrupted
|
||||
}
|
||||
}
|
||||
if parent == nil {
|
||||
return errors.New("missing parent")
|
||||
}
|
||||
// Import all the pruned blocks to make the state available
|
||||
for i := len(hashes) - 1; i >= 0; i-- {
|
||||
// If the chain is terminating, stop processing blocks
|
||||
if bc.insertStopped() {
|
||||
log.Debug("Abort during blocks processing")
|
||||
return errInsertionInterrupted
|
||||
}
|
||||
var b *types.Block
|
||||
if i == 0 {
|
||||
b = block
|
||||
} else {
|
||||
b = bc.GetBlock(hashes[i], numbers[i])
|
||||
}
|
||||
if _, err := bc.insertChain(types.Blocks{b}, false, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectLogs collects the logs that were generated or removed during
|
||||
// the processing of the block that corresponds with the given hash.
|
||||
// These logs are later announced as deleted or reborn.
|
||||
func (bc *BlockChain) collectLogs(hash common.Hash, removed bool) []*types.Log {
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
receipts := rawdb.ReadReceipts(bc.db, hash, *number, bc.chainConfig)
|
||||
|
||||
var logs []*types.Log
|
||||
for _, receipt := range receipts {
|
||||
for _, log := range receipt.Logs {
|
||||
l := *log
|
||||
if removed {
|
||||
l.Removed = true
|
||||
}
|
||||
logs = append(logs, &l)
|
||||
}
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
// mergeLogs returns a merged log slice with specified sort order.
|
||||
func mergeLogs(logs [][]*types.Log, reverse bool) []*types.Log {
|
||||
var ret []*types.Log
|
||||
if reverse {
|
||||
for i := len(logs) - 1; i >= 0; i-- {
|
||||
ret = append(ret, logs[i]...)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(logs); i++ {
|
||||
ret = append(ret, logs[i]...)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// reorg takes two blocks, an old chain and a new chain and will reconstruct the
|
||||
// blocks and inserts them to be part of the new canonical chain and accumulates
|
||||
// potential missing transactions and post an event about them.
|
||||
// Note the new head block won't be processed here, callers need to handle it
|
||||
// externally.
|
||||
func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
var (
|
||||
newChain types.Blocks
|
||||
@@ -1848,49 +1935,6 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
|
||||
deletedLogs [][]*types.Log
|
||||
rebirthLogs [][]*types.Log
|
||||
|
||||
// collectLogs collects the logs that were generated or removed during
|
||||
// the processing of the block that corresponds with the given hash.
|
||||
// These logs are later announced as deleted or reborn
|
||||
collectLogs = func(hash common.Hash, removed bool) {
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return
|
||||
}
|
||||
receipts := rawdb.ReadReceipts(bc.db, hash, *number, bc.chainConfig)
|
||||
|
||||
var logs []*types.Log
|
||||
for _, receipt := range receipts {
|
||||
for _, log := range receipt.Logs {
|
||||
l := *log
|
||||
if removed {
|
||||
l.Removed = true
|
||||
}
|
||||
logs = append(logs, &l)
|
||||
}
|
||||
}
|
||||
if len(logs) > 0 {
|
||||
if removed {
|
||||
deletedLogs = append(deletedLogs, logs)
|
||||
} else {
|
||||
rebirthLogs = append(rebirthLogs, logs)
|
||||
}
|
||||
}
|
||||
}
|
||||
// mergeLogs returns a merged log slice with specified sort order.
|
||||
mergeLogs = func(logs [][]*types.Log, reverse bool) []*types.Log {
|
||||
var ret []*types.Log
|
||||
if reverse {
|
||||
for i := len(logs) - 1; i >= 0; i-- {
|
||||
ret = append(ret, logs[i]...)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(logs); i++ {
|
||||
ret = append(ret, logs[i]...)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
)
|
||||
// Reduce the longer chain to the same number as the shorter one
|
||||
if oldBlock.NumberU64() > newBlock.NumberU64() {
|
||||
@@ -1898,7 +1942,12 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
for ; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) {
|
||||
oldChain = append(oldChain, oldBlock)
|
||||
deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
|
||||
collectLogs(oldBlock.Hash(), true)
|
||||
|
||||
// Collect deleted logs for notification
|
||||
logs := bc.collectLogs(oldBlock.Hash(), true)
|
||||
if len(logs) > 0 {
|
||||
deletedLogs = append(deletedLogs, logs)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// New chain is longer, stash all blocks away for subsequent insertion
|
||||
@@ -1923,8 +1972,12 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
// Remove an old block as well as stash away a new block
|
||||
oldChain = append(oldChain, oldBlock)
|
||||
deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
|
||||
collectLogs(oldBlock.Hash(), true)
|
||||
|
||||
// Collect deleted logs for notification
|
||||
logs := bc.collectLogs(oldBlock.Hash(), true)
|
||||
if len(logs) > 0 {
|
||||
deletedLogs = append(deletedLogs, logs)
|
||||
}
|
||||
newChain = append(newChain, newBlock)
|
||||
|
||||
// Step back with both chains
|
||||
@@ -1951,8 +2004,15 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
blockReorgAddMeter.Mark(int64(len(newChain)))
|
||||
blockReorgDropMeter.Mark(int64(len(oldChain)))
|
||||
blockReorgMeter.Mark(1)
|
||||
} else if len(newChain) > 0 {
|
||||
// Special case happens in the post merge stage that current head is
|
||||
// the ancestor of new head while these two blocks are not consecutive
|
||||
log.Info("Extend chain", "add", len(newChain), "number", newChain[0].NumberU64(), "hash", newChain[0].Hash())
|
||||
blockReorgAddMeter.Mark(int64(len(newChain)))
|
||||
} else {
|
||||
log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "newnum", newBlock.Number(), "newhash", newBlock.Hash())
|
||||
// len(newChain) == 0 && len(oldChain) > 0
|
||||
// rewind the canonical chain to a lower point.
|
||||
log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain))
|
||||
}
|
||||
// Insert the new chain(except the head block(reverse order)),
|
||||
// taking care of the proper incremental order.
|
||||
@@ -1961,8 +2021,10 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
bc.writeHeadBlock(newChain[i])
|
||||
|
||||
// Collect reborn logs due to chain reorg
|
||||
collectLogs(newChain[i].Hash(), false)
|
||||
|
||||
logs := bc.collectLogs(newChain[i].Hash(), false)
|
||||
if len(logs) > 0 {
|
||||
rebirthLogs = append(rebirthLogs, logs)
|
||||
}
|
||||
// Collect the new added transactions.
|
||||
addedTxs = append(addedTxs, newChain[i].Transactions()...)
|
||||
}
|
||||
@@ -2002,12 +2064,54 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// futureBlocksLoop processes the 'future block' queue.
|
||||
func (bc *BlockChain) futureBlocksLoop() {
|
||||
defer bc.wg.Done()
|
||||
// InsertBlockWithoutSetHead executes the block, runs the necessary verification
|
||||
// upon it and then persist the block and the associate state into the database.
|
||||
// The key difference between the InsertChain is it won't do the canonical chain
|
||||
// updating. It relies on the additional SetChainHead call to finalize the entire
|
||||
// procedure.
|
||||
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block) error {
|
||||
if !bc.chainmu.TryLock() {
|
||||
return errChainStopped
|
||||
}
|
||||
defer bc.chainmu.Unlock()
|
||||
|
||||
_, err := bc.insertChain(types.Blocks{block}, true, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetChainHead rewinds the chain to set the new head block as the specified
|
||||
// block. It's possible that after the reorg the relevant state of head
|
||||
// is missing. It can be fixed by inserting a new block which triggers
|
||||
// the re-execution.
|
||||
func (bc *BlockChain) SetChainHead(newBlock *types.Block) error {
|
||||
if !bc.chainmu.TryLock() {
|
||||
return errChainStopped
|
||||
}
|
||||
defer bc.chainmu.Unlock()
|
||||
|
||||
// Run the reorg if necessary and set the given block as new head.
|
||||
if newBlock.ParentHash() != bc.CurrentBlock().Hash() {
|
||||
if err := bc.reorg(bc.CurrentBlock(), newBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
bc.writeHeadBlock(newBlock)
|
||||
|
||||
// Emit events
|
||||
logs := bc.collectLogs(newBlock.Hash(), false)
|
||||
bc.chainFeed.Send(ChainEvent{Block: newBlock, Hash: newBlock.Hash(), Logs: logs})
|
||||
if len(logs) > 0 {
|
||||
bc.logsFeed.Send(logs)
|
||||
}
|
||||
bc.chainHeadFeed.Send(ChainHeadEvent{Block: newBlock})
|
||||
log.Info("Set the chain head", "number", newBlock.Number(), "hash", newBlock.Hash())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bc *BlockChain) updateFutureBlocks() {
|
||||
futureTimer := time.NewTicker(5 * time.Second)
|
||||
defer futureTimer.Stop()
|
||||
defer bc.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-futureTimer.C:
|
||||
@@ -2103,7 +2207,14 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
|
||||
// If a previous indexing existed, make sure that we fill in any missing entries
|
||||
if bc.txLookupLimit == 0 || head < bc.txLookupLimit {
|
||||
if *tail > 0 {
|
||||
rawdb.IndexTransactions(bc.db, 0, *tail, bc.quit)
|
||||
// It can happen when chain is rewound to a historical point which
|
||||
// is even lower than the indexes tail, recap the indexing target
|
||||
// to new head to avoid reading non-existent block bodies.
|
||||
end := *tail
|
||||
if end > head+1 {
|
||||
end = head + 1
|
||||
}
|
||||
rawdb.IndexTransactions(bc.db, 0, end, bc.quit)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -2188,6 +2299,6 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
|
||||
return 0, errChainStopped
|
||||
}
|
||||
defer bc.chainmu.Unlock()
|
||||
_, err := bc.hc.InsertHeaderChain(chain, start)
|
||||
_, err := bc.hc.InsertHeaderChain(chain, start, bc.forker)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -73,6 +73,12 @@ func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
|
||||
return bc.hc.GetHeaderByNumber(number)
|
||||
}
|
||||
|
||||
// GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going
|
||||
// backwards from the given number.
|
||||
func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
|
||||
return bc.hc.GetHeadersFrom(number, count)
|
||||
}
|
||||
|
||||
// GetBody retrieves a block body (transactions and uncles) from the database by
|
||||
// hash, caching it if found.
|
||||
func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {
|
||||
|
||||
+113
-113
@@ -79,10 +79,10 @@ func testShortRepair(t *testing.T, snapshots bool) {
|
||||
// already committed, after which the process crashed. In this case we expect the full
|
||||
// chain to be rolled back to the committed block, but the chain data itself left in
|
||||
// the database for replaying.
|
||||
func TestShortFastSyncedRepair(t *testing.T) { testShortFastSyncedRepair(t, false) }
|
||||
func TestShortFastSyncedRepairWithSnapshots(t *testing.T) { testShortFastSyncedRepair(t, true) }
|
||||
func TestShortSnapSyncedRepair(t *testing.T) { testShortSnapSyncedRepair(t, false) }
|
||||
func TestShortSnapSyncedRepairWithSnapshots(t *testing.T) { testShortSnapSyncedRepair(t, true) }
|
||||
|
||||
func testShortFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||
func testShortSnapSyncedRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
@@ -119,10 +119,10 @@ func testShortFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||
// not yet committed, but the process crashed. In this case we expect the chain to
|
||||
// detect that it was fast syncing and not delete anything, since we can just pick
|
||||
// up directly where we left off.
|
||||
func TestShortFastSyncingRepair(t *testing.T) { testShortFastSyncingRepair(t, false) }
|
||||
func TestShortFastSyncingRepairWithSnapshots(t *testing.T) { testShortFastSyncingRepair(t, true) }
|
||||
func TestShortSnapSyncingRepair(t *testing.T) { testShortSnapSyncingRepair(t, false) }
|
||||
func TestShortSnapSyncingRepairWithSnapshots(t *testing.T) { testShortSnapSyncingRepair(t, true) }
|
||||
|
||||
func testShortFastSyncingRepair(t *testing.T, snapshots bool) {
|
||||
func testShortSnapSyncingRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
@@ -203,14 +203,14 @@ func testShortOldForkedRepair(t *testing.T, snapshots bool) {
|
||||
// crashed. In this test scenario the side chain is below the committed block. In
|
||||
// this case we expect the canonical chain to be rolled back to the committed block,
|
||||
// but the chain data itself left in the database for replaying.
|
||||
func TestShortOldForkedFastSyncedRepair(t *testing.T) {
|
||||
testShortOldForkedFastSyncedRepair(t, false)
|
||||
func TestShortOldForkedSnapSyncedRepair(t *testing.T) {
|
||||
testShortOldForkedSnapSyncedRepair(t, false)
|
||||
}
|
||||
func TestShortOldForkedFastSyncedRepairWithSnapshots(t *testing.T) {
|
||||
testShortOldForkedFastSyncedRepair(t, true)
|
||||
func TestShortOldForkedSnapSyncedRepairWithSnapshots(t *testing.T) {
|
||||
testShortOldForkedSnapSyncedRepair(t, true)
|
||||
}
|
||||
|
||||
func testShortOldForkedFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||
func testShortOldForkedSnapSyncedRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -250,14 +250,14 @@ func testShortOldForkedFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||
// test scenario the side chain is below the committed block. In this case we expect
|
||||
// the chain to detect that it was fast syncing and not delete anything, since we
|
||||
// can just pick up directly where we left off.
|
||||
func TestShortOldForkedFastSyncingRepair(t *testing.T) {
|
||||
testShortOldForkedFastSyncingRepair(t, false)
|
||||
func TestShortOldForkedSnapSyncingRepair(t *testing.T) {
|
||||
testShortOldForkedSnapSyncingRepair(t, false)
|
||||
}
|
||||
func TestShortOldForkedFastSyncingRepairWithSnapshots(t *testing.T) {
|
||||
testShortOldForkedFastSyncingRepair(t, true)
|
||||
func TestShortOldForkedSnapSyncingRepairWithSnapshots(t *testing.T) {
|
||||
testShortOldForkedSnapSyncingRepair(t, true)
|
||||
}
|
||||
|
||||
func testShortOldForkedFastSyncingRepair(t *testing.T, snapshots bool) {
|
||||
func testShortOldForkedSnapSyncingRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -340,14 +340,14 @@ func testShortNewlyForkedRepair(t *testing.T, snapshots bool) {
|
||||
// crashed. In this test scenario the side chain reaches above the committed block.
|
||||
// In this case we expect the canonical chain to be rolled back to the committed
|
||||
// block, but the chain data itself left in the database for replaying.
|
||||
func TestShortNewlyForkedFastSyncedRepair(t *testing.T) {
|
||||
testShortNewlyForkedFastSyncedRepair(t, false)
|
||||
func TestShortNewlyForkedSnapSyncedRepair(t *testing.T) {
|
||||
testShortNewlyForkedSnapSyncedRepair(t, false)
|
||||
}
|
||||
func TestShortNewlyForkedFastSyncedRepairWithSnapshots(t *testing.T) {
|
||||
testShortNewlyForkedFastSyncedRepair(t, true)
|
||||
func TestShortNewlyForkedSnapSyncedRepairWithSnapshots(t *testing.T) {
|
||||
testShortNewlyForkedSnapSyncedRepair(t, true)
|
||||
}
|
||||
|
||||
func testShortNewlyForkedFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||
func testShortNewlyForkedSnapSyncedRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6
|
||||
@@ -387,14 +387,14 @@ func testShortNewlyForkedFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||
// this test scenario the side chain reaches above the committed block. In this
|
||||
// case we expect the chain to detect that it was fast syncing and not delete
|
||||
// anything, since we can just pick up directly where we left off.
|
||||
func TestShortNewlyForkedFastSyncingRepair(t *testing.T) {
|
||||
testShortNewlyForkedFastSyncingRepair(t, false)
|
||||
func TestShortNewlyForkedSnapSyncingRepair(t *testing.T) {
|
||||
testShortNewlyForkedSnapSyncingRepair(t, false)
|
||||
}
|
||||
func TestShortNewlyForkedFastSyncingRepairWithSnapshots(t *testing.T) {
|
||||
testShortNewlyForkedFastSyncingRepair(t, true)
|
||||
func TestShortNewlyForkedSnapSyncingRepairWithSnapshots(t *testing.T) {
|
||||
testShortNewlyForkedSnapSyncingRepair(t, true)
|
||||
}
|
||||
|
||||
func testShortNewlyForkedFastSyncingRepair(t *testing.T, snapshots bool) {
|
||||
func testShortNewlyForkedSnapSyncingRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6
|
||||
@@ -475,14 +475,14 @@ func testShortReorgedRepair(t *testing.T, snapshots bool) {
|
||||
// the fast sync pivot point was already committed to disk and then the process
|
||||
// crashed. In this case we expect the canonical chain to be rolled back to the
|
||||
// committed block, but the chain data itself left in the database for replaying.
|
||||
func TestShortReorgedFastSyncedRepair(t *testing.T) {
|
||||
testShortReorgedFastSyncedRepair(t, false)
|
||||
func TestShortReorgedSnapSyncedRepair(t *testing.T) {
|
||||
testShortReorgedSnapSyncedRepair(t, false)
|
||||
}
|
||||
func TestShortReorgedFastSyncedRepairWithSnapshots(t *testing.T) {
|
||||
testShortReorgedFastSyncedRepair(t, true)
|
||||
func TestShortReorgedSnapSyncedRepairWithSnapshots(t *testing.T) {
|
||||
testShortReorgedSnapSyncedRepair(t, true)
|
||||
}
|
||||
|
||||
func testShortReorgedFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||
func testShortReorgedSnapSyncedRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||
@@ -521,14 +521,14 @@ func testShortReorgedFastSyncedRepair(t *testing.T, snapshots bool) {
|
||||
// the fast sync pivot point was not yet committed, but the process crashed. In
|
||||
// this case we expect the chain to detect that it was fast syncing and not delete
|
||||
// anything, since we can just pick up directly where we left off.
|
||||
func TestShortReorgedFastSyncingRepair(t *testing.T) {
|
||||
testShortReorgedFastSyncingRepair(t, false)
|
||||
func TestShortReorgedSnapSyncingRepair(t *testing.T) {
|
||||
testShortReorgedSnapSyncingRepair(t, false)
|
||||
}
|
||||
func TestShortReorgedFastSyncingRepairWithSnapshots(t *testing.T) {
|
||||
testShortReorgedFastSyncingRepair(t, true)
|
||||
func TestShortReorgedSnapSyncingRepairWithSnapshots(t *testing.T) {
|
||||
testShortReorgedSnapSyncingRepair(t, true)
|
||||
}
|
||||
|
||||
func testShortReorgedFastSyncingRepair(t *testing.T, snapshots bool) {
|
||||
func testShortReorgedSnapSyncingRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||
@@ -656,14 +656,14 @@ func testLongDeepRepair(t *testing.T, snapshots bool) {
|
||||
// sync pivot point - newer than the ancient limit - was already committed, after
|
||||
// which the process crashed. In this case we expect the chain to be rolled back
|
||||
// to the committed block, with everything afterwads kept as fast sync data.
|
||||
func TestLongFastSyncedShallowRepair(t *testing.T) {
|
||||
testLongFastSyncedShallowRepair(t, false)
|
||||
func TestLongSnapSyncedShallowRepair(t *testing.T) {
|
||||
testLongSnapSyncedShallowRepair(t, false)
|
||||
}
|
||||
func TestLongFastSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongFastSyncedShallowRepair(t, true)
|
||||
func TestLongSnapSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongSnapSyncedShallowRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
func testLongSnapSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
//
|
||||
@@ -705,10 +705,10 @@ func testLongFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// sync pivot point - older than the ancient limit - was already committed, after
|
||||
// which the process crashed. In this case we expect the chain to be rolled back
|
||||
// to the committed block, with everything afterwads deleted.
|
||||
func TestLongFastSyncedDeepRepair(t *testing.T) { testLongFastSyncedDeepRepair(t, false) }
|
||||
func TestLongFastSyncedDeepRepairWithSnapshots(t *testing.T) { testLongFastSyncedDeepRepair(t, true) }
|
||||
func TestLongSnapSyncedDeepRepair(t *testing.T) { testLongSnapSyncedDeepRepair(t, false) }
|
||||
func TestLongSnapSyncedDeepRepairWithSnapshots(t *testing.T) { testLongSnapSyncedDeepRepair(t, true) }
|
||||
|
||||
func testLongFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
func testLongSnapSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
//
|
||||
@@ -750,14 +750,14 @@ func testLongFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// process crashed. In this case we expect the chain to detect that it was fast
|
||||
// syncing and not delete anything, since we can just pick up directly where we
|
||||
// left off.
|
||||
func TestLongFastSyncingShallowRepair(t *testing.T) {
|
||||
testLongFastSyncingShallowRepair(t, false)
|
||||
func TestLongSnapSyncingShallowRepair(t *testing.T) {
|
||||
testLongSnapSyncingShallowRepair(t, false)
|
||||
}
|
||||
func TestLongFastSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongFastSyncingShallowRepair(t, true)
|
||||
func TestLongSnapSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongSnapSyncingShallowRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
func testLongSnapSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
//
|
||||
@@ -800,10 +800,10 @@ func testLongFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
// process crashed. In this case we expect the chain to detect that it was fast
|
||||
// syncing and not delete anything, since we can just pick up directly where we
|
||||
// left off.
|
||||
func TestLongFastSyncingDeepRepair(t *testing.T) { testLongFastSyncingDeepRepair(t, false) }
|
||||
func TestLongFastSyncingDeepRepairWithSnapshots(t *testing.T) { testLongFastSyncingDeepRepair(t, true) }
|
||||
func TestLongSnapSyncingDeepRepair(t *testing.T) { testLongSnapSyncingDeepRepair(t, false) }
|
||||
func TestLongSnapSyncingDeepRepairWithSnapshots(t *testing.T) { testLongSnapSyncingDeepRepair(t, true) }
|
||||
|
||||
func testLongFastSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||
func testLongSnapSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
//
|
||||
@@ -946,14 +946,14 @@ func testLongOldForkedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// the side chain is below the committed block. In this case we expect the chain
|
||||
// to be rolled back to the committed block, with everything afterwads kept as
|
||||
// fast sync data; the side chain completely nuked by the freezer.
|
||||
func TestLongOldForkedFastSyncedShallowRepair(t *testing.T) {
|
||||
testLongOldForkedFastSyncedShallowRepair(t, false)
|
||||
func TestLongOldForkedSnapSyncedShallowRepair(t *testing.T) {
|
||||
testLongOldForkedSnapSyncedShallowRepair(t, false)
|
||||
}
|
||||
func TestLongOldForkedFastSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedFastSyncedShallowRepair(t, true)
|
||||
func TestLongOldForkedSnapSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedSnapSyncedShallowRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongOldForkedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
func testLongOldForkedSnapSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -998,14 +998,14 @@ func testLongOldForkedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// the side chain is below the committed block. In this case we expect the canonical
|
||||
// chain to be rolled back to the committed block, with everything afterwads deleted;
|
||||
// the side chain completely nuked by the freezer.
|
||||
func TestLongOldForkedFastSyncedDeepRepair(t *testing.T) {
|
||||
testLongOldForkedFastSyncedDeepRepair(t, false)
|
||||
func TestLongOldForkedSnapSyncedDeepRepair(t *testing.T) {
|
||||
testLongOldForkedSnapSyncedDeepRepair(t, false)
|
||||
}
|
||||
func TestLongOldForkedFastSyncedDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedFastSyncedDeepRepair(t, true)
|
||||
func TestLongOldForkedSnapSyncedDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedSnapSyncedDeepRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongOldForkedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
func testLongOldForkedSnapSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -1049,14 +1049,14 @@ func testLongOldForkedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// chain is below the committed block. In this case we expect the chain to detect
|
||||
// that it was fast syncing and not delete anything. The side chain is completely
|
||||
// nuked by the freezer.
|
||||
func TestLongOldForkedFastSyncingShallowRepair(t *testing.T) {
|
||||
testLongOldForkedFastSyncingShallowRepair(t, false)
|
||||
func TestLongOldForkedSnapSyncingShallowRepair(t *testing.T) {
|
||||
testLongOldForkedSnapSyncingShallowRepair(t, false)
|
||||
}
|
||||
func TestLongOldForkedFastSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedFastSyncingShallowRepair(t, true)
|
||||
func TestLongOldForkedSnapSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedSnapSyncingShallowRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongOldForkedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
func testLongOldForkedSnapSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -1101,14 +1101,14 @@ func testLongOldForkedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
// chain is below the committed block. In this case we expect the chain to detect
|
||||
// that it was fast syncing and not delete anything. The side chain is completely
|
||||
// nuked by the freezer.
|
||||
func TestLongOldForkedFastSyncingDeepRepair(t *testing.T) {
|
||||
testLongOldForkedFastSyncingDeepRepair(t, false)
|
||||
func TestLongOldForkedSnapSyncingDeepRepair(t *testing.T) {
|
||||
testLongOldForkedSnapSyncingDeepRepair(t, false)
|
||||
}
|
||||
func TestLongOldForkedFastSyncingDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedFastSyncingDeepRepair(t, true)
|
||||
func TestLongOldForkedSnapSyncingDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedSnapSyncingDeepRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongOldForkedFastSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||
func testLongOldForkedSnapSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -1252,14 +1252,14 @@ func testLongNewerForkedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// the side chain is above the committed block. In this case we expect the chain
|
||||
// to be rolled back to the committed block, with everything afterwads kept as fast
|
||||
// sync data; the side chain completely nuked by the freezer.
|
||||
func TestLongNewerForkedFastSyncedShallowRepair(t *testing.T) {
|
||||
testLongNewerForkedFastSyncedShallowRepair(t, false)
|
||||
func TestLongNewerForkedSnapSyncedShallowRepair(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncedShallowRepair(t, false)
|
||||
}
|
||||
func TestLongNewerForkedFastSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedFastSyncedShallowRepair(t, true)
|
||||
func TestLongNewerForkedSnapSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncedShallowRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongNewerForkedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
func testLongNewerForkedSnapSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
@@ -1304,14 +1304,14 @@ func testLongNewerForkedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// the side chain is above the committed block. In this case we expect the canonical
|
||||
// chain to be rolled back to the committed block, with everything afterwads deleted;
|
||||
// the side chain completely nuked by the freezer.
|
||||
func TestLongNewerForkedFastSyncedDeepRepair(t *testing.T) {
|
||||
testLongNewerForkedFastSyncedDeepRepair(t, false)
|
||||
func TestLongNewerForkedSnapSyncedDeepRepair(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncedDeepRepair(t, false)
|
||||
}
|
||||
func TestLongNewerForkedFastSyncedDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedFastSyncedDeepRepair(t, true)
|
||||
func TestLongNewerForkedSnapSyncedDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncedDeepRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongNewerForkedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
func testLongNewerForkedSnapSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
@@ -1355,14 +1355,14 @@ func testLongNewerForkedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// chain is above the committed block. In this case we expect the chain to detect
|
||||
// that it was fast syncing and not delete anything. The side chain is completely
|
||||
// nuked by the freezer.
|
||||
func TestLongNewerForkedFastSyncingShallowRepair(t *testing.T) {
|
||||
testLongNewerForkedFastSyncingShallowRepair(t, false)
|
||||
func TestLongNewerForkedSnapSyncingShallowRepair(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncingShallowRepair(t, false)
|
||||
}
|
||||
func TestLongNewerForkedFastSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedFastSyncingShallowRepair(t, true)
|
||||
func TestLongNewerForkedSnapSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncingShallowRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongNewerForkedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
func testLongNewerForkedSnapSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
@@ -1407,14 +1407,14 @@ func testLongNewerForkedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
// chain is above the committed block. In this case we expect the chain to detect
|
||||
// that it was fast syncing and not delete anything. The side chain is completely
|
||||
// nuked by the freezer.
|
||||
func TestLongNewerForkedFastSyncingDeepRepair(t *testing.T) {
|
||||
testLongNewerForkedFastSyncingDeepRepair(t, false)
|
||||
func TestLongNewerForkedSnapSyncingDeepRepair(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncingDeepRepair(t, false)
|
||||
}
|
||||
func TestLongNewerForkedFastSyncingDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedFastSyncingDeepRepair(t, true)
|
||||
func TestLongNewerForkedSnapSyncingDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncingDeepRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongNewerForkedFastSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||
func testLongNewerForkedSnapSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
@@ -1552,14 +1552,14 @@ func testLongReorgedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// expect the chain to be rolled back to the committed block, with everything
|
||||
// afterwads kept as fast sync data. The side chain completely nuked by the
|
||||
// freezer.
|
||||
func TestLongReorgedFastSyncedShallowRepair(t *testing.T) {
|
||||
testLongReorgedFastSyncedShallowRepair(t, false)
|
||||
func TestLongReorgedSnapSyncedShallowRepair(t *testing.T) {
|
||||
testLongReorgedSnapSyncedShallowRepair(t, false)
|
||||
}
|
||||
func TestLongReorgedFastSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongReorgedFastSyncedShallowRepair(t, true)
|
||||
func TestLongReorgedSnapSyncedShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongReorgedSnapSyncedShallowRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongReorgedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
func testLongReorgedSnapSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
@@ -1603,14 +1603,14 @@ func testLongReorgedFastSyncedShallowRepair(t *testing.T, snapshots bool) {
|
||||
// was already committed to disk and then the process crashed. In this case we
|
||||
// expect the canonical chains to be rolled back to the committed block, with
|
||||
// everything afterwads deleted. The side chain completely nuked by the freezer.
|
||||
func TestLongReorgedFastSyncedDeepRepair(t *testing.T) {
|
||||
testLongReorgedFastSyncedDeepRepair(t, false)
|
||||
func TestLongReorgedSnapSyncedDeepRepair(t *testing.T) {
|
||||
testLongReorgedSnapSyncedDeepRepair(t, false)
|
||||
}
|
||||
func TestLongReorgedFastSyncedDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongReorgedFastSyncedDeepRepair(t, true)
|
||||
func TestLongReorgedSnapSyncedDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongReorgedSnapSyncedDeepRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongReorgedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
func testLongReorgedSnapSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
@@ -1653,14 +1653,14 @@ func testLongReorgedFastSyncedDeepRepair(t *testing.T, snapshots bool) {
|
||||
// was not yet committed, but the process crashed. In this case we expect the
|
||||
// chain to detect that it was fast syncing and not delete anything, since we
|
||||
// can just pick up directly where we left off.
|
||||
func TestLongReorgedFastSyncingShallowRepair(t *testing.T) {
|
||||
testLongReorgedFastSyncingShallowRepair(t, false)
|
||||
func TestLongReorgedSnapSyncingShallowRepair(t *testing.T) {
|
||||
testLongReorgedSnapSyncingShallowRepair(t, false)
|
||||
}
|
||||
func TestLongReorgedFastSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongReorgedFastSyncingShallowRepair(t, true)
|
||||
func TestLongReorgedSnapSyncingShallowRepairWithSnapshots(t *testing.T) {
|
||||
testLongReorgedSnapSyncingShallowRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongReorgedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
func testLongReorgedSnapSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
@@ -1704,14 +1704,14 @@ func testLongReorgedFastSyncingShallowRepair(t *testing.T, snapshots bool) {
|
||||
// was not yet committed, but the process crashed. In this case we expect the
|
||||
// chain to detect that it was fast syncing and not delete anything, since we
|
||||
// can just pick up directly where we left off.
|
||||
func TestLongReorgedFastSyncingDeepRepair(t *testing.T) {
|
||||
testLongReorgedFastSyncingDeepRepair(t, false)
|
||||
func TestLongReorgedSnapSyncingDeepRepair(t *testing.T) {
|
||||
testLongReorgedSnapSyncingDeepRepair(t, false)
|
||||
}
|
||||
func TestLongReorgedFastSyncingDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongReorgedFastSyncingDeepRepair(t, true)
|
||||
func TestLongReorgedSnapSyncingDeepRepairWithSnapshots(t *testing.T) {
|
||||
testLongReorgedSnapSyncingDeepRepair(t, true)
|
||||
}
|
||||
|
||||
func testLongReorgedFastSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||
func testLongReorgedSnapSyncingDeepRepair(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
@@ -1829,7 +1829,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
|
||||
// Pull the plug on the database, simulating a hard crash
|
||||
db.Close()
|
||||
|
||||
// Start a new blockchain back up and see where the repait leads us
|
||||
// Start a new blockchain back up and see where the repair leads us
|
||||
db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to reopen persistent database: %v", err)
|
||||
|
||||
+120
-120
@@ -194,10 +194,10 @@ func testShortSetHead(t *testing.T, snapshots bool) {
|
||||
// Everything above the sethead point should be deleted. In between the committed
|
||||
// block and the requested head the data can remain as "fast sync" data to avoid
|
||||
// redownloading it.
|
||||
func TestShortFastSyncedSetHead(t *testing.T) { testShortFastSyncedSetHead(t, false) }
|
||||
func TestShortFastSyncedSetHeadWithSnapshots(t *testing.T) { testShortFastSyncedSetHead(t, true) }
|
||||
func TestShortSnapSyncedSetHead(t *testing.T) { testShortSnapSyncedSetHead(t, false) }
|
||||
func TestShortSnapSyncedSetHeadWithSnapshots(t *testing.T) { testShortSnapSyncedSetHead(t, true) }
|
||||
|
||||
func testShortFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
func testShortSnapSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
@@ -236,10 +236,10 @@ func testShortFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
// detect that it was fast syncing and delete everything from the new head, since
|
||||
// we can just pick up fast syncing from there. The head full block should be set
|
||||
// to the genesis.
|
||||
func TestShortFastSyncingSetHead(t *testing.T) { testShortFastSyncingSetHead(t, false) }
|
||||
func TestShortFastSyncingSetHeadWithSnapshots(t *testing.T) { testShortFastSyncingSetHead(t, true) }
|
||||
func TestShortSnapSyncingSetHead(t *testing.T) { testShortSnapSyncingSetHead(t, false) }
|
||||
func TestShortSnapSyncingSetHeadWithSnapshots(t *testing.T) { testShortSnapSyncingSetHead(t, true) }
|
||||
|
||||
func testShortFastSyncingSetHead(t *testing.T, snapshots bool) {
|
||||
func testShortSnapSyncingSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
//
|
||||
@@ -326,14 +326,14 @@ func testShortOldForkedSetHead(t *testing.T, snapshots bool) {
|
||||
// block. Everything above the sethead point should be deleted. In between the
|
||||
// committed block and the requested head the data can remain as "fast sync" data
|
||||
// to avoid redownloading it. The side chain should be left alone as it was shorter.
|
||||
func TestShortOldForkedFastSyncedSetHead(t *testing.T) {
|
||||
testShortOldForkedFastSyncedSetHead(t, false)
|
||||
func TestShortOldForkedSnapSyncedSetHead(t *testing.T) {
|
||||
testShortOldForkedSnapSyncedSetHead(t, false)
|
||||
}
|
||||
func TestShortOldForkedFastSyncedSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortOldForkedFastSyncedSetHead(t, true)
|
||||
func TestShortOldForkedSnapSyncedSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortOldForkedSnapSyncedSetHead(t, true)
|
||||
}
|
||||
|
||||
func testShortOldForkedFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
func testShortOldForkedSnapSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -375,14 +375,14 @@ func testShortOldForkedFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
// the chain to detect that it was fast syncing and delete everything from the new
|
||||
// head, since we can just pick up fast syncing from there. The head full block
|
||||
// should be set to the genesis.
|
||||
func TestShortOldForkedFastSyncingSetHead(t *testing.T) {
|
||||
testShortOldForkedFastSyncingSetHead(t, false)
|
||||
func TestShortOldForkedSnapSyncingSetHead(t *testing.T) {
|
||||
testShortOldForkedSnapSyncingSetHead(t, false)
|
||||
}
|
||||
func TestShortOldForkedFastSyncingSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortOldForkedFastSyncingSetHead(t, true)
|
||||
func TestShortOldForkedSnapSyncingSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortOldForkedSnapSyncingSetHead(t, true)
|
||||
}
|
||||
|
||||
func testShortOldForkedFastSyncingSetHead(t *testing.T, snapshots bool) {
|
||||
func testShortOldForkedSnapSyncingSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -478,14 +478,14 @@ func testShortNewlyForkedSetHead(t *testing.T, snapshots bool) {
|
||||
// The side chain could be left to be if the fork point was before the new head
|
||||
// we are deleting to, but it would be exceedingly hard to detect that case and
|
||||
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
||||
func TestShortNewlyForkedFastSyncedSetHead(t *testing.T) {
|
||||
testShortNewlyForkedFastSyncedSetHead(t, false)
|
||||
func TestShortNewlyForkedSnapSyncedSetHead(t *testing.T) {
|
||||
testShortNewlyForkedSnapSyncedSetHead(t, false)
|
||||
}
|
||||
func TestShortNewlyForkedFastSyncedSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortNewlyForkedFastSyncedSetHead(t, true)
|
||||
func TestShortNewlyForkedSnapSyncedSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortNewlyForkedSnapSyncedSetHead(t, true)
|
||||
}
|
||||
|
||||
func testShortNewlyForkedFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
func testShortNewlyForkedSnapSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8
|
||||
@@ -531,14 +531,14 @@ func testShortNewlyForkedFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
// The side chain could be left to be if the fork point was before the new head
|
||||
// we are deleting to, but it would be exceedingly hard to detect that case and
|
||||
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
||||
func TestShortNewlyForkedFastSyncingSetHead(t *testing.T) {
|
||||
testShortNewlyForkedFastSyncingSetHead(t, false)
|
||||
func TestShortNewlyForkedSnapSyncingSetHead(t *testing.T) {
|
||||
testShortNewlyForkedSnapSyncingSetHead(t, false)
|
||||
}
|
||||
func TestShortNewlyForkedFastSyncingSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortNewlyForkedFastSyncingSetHead(t, true)
|
||||
func TestShortNewlyForkedSnapSyncingSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortNewlyForkedSnapSyncingSetHead(t, true)
|
||||
}
|
||||
|
||||
func testShortNewlyForkedFastSyncingSetHead(t *testing.T, snapshots bool) {
|
||||
func testShortNewlyForkedSnapSyncingSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8
|
||||
@@ -634,14 +634,14 @@ func testShortReorgedSetHead(t *testing.T, snapshots bool) {
|
||||
// The side chain could be left to be if the fork point was before the new head
|
||||
// we are deleting to, but it would be exceedingly hard to detect that case and
|
||||
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
||||
func TestShortReorgedFastSyncedSetHead(t *testing.T) {
|
||||
testShortReorgedFastSyncedSetHead(t, false)
|
||||
func TestShortReorgedSnapSyncedSetHead(t *testing.T) {
|
||||
testShortReorgedSnapSyncedSetHead(t, false)
|
||||
}
|
||||
func TestShortReorgedFastSyncedSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortReorgedFastSyncedSetHead(t, true)
|
||||
func TestShortReorgedSnapSyncedSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortReorgedSnapSyncedSetHead(t, true)
|
||||
}
|
||||
|
||||
func testShortReorgedFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
func testShortReorgedSnapSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||
@@ -686,14 +686,14 @@ func testShortReorgedFastSyncedSetHead(t *testing.T, snapshots bool) {
|
||||
// The side chain could be left to be if the fork point was before the new head
|
||||
// we are deleting to, but it would be exceedingly hard to detect that case and
|
||||
// properly handle it, so we'll trade extra work in exchange for simpler code.
|
||||
func TestShortReorgedFastSyncingSetHead(t *testing.T) {
|
||||
testShortReorgedFastSyncingSetHead(t, false)
|
||||
func TestShortReorgedSnapSyncingSetHead(t *testing.T) {
|
||||
testShortReorgedSnapSyncingSetHead(t, false)
|
||||
}
|
||||
func TestShortReorgedFastSyncingSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortReorgedFastSyncingSetHead(t, true)
|
||||
func TestShortReorgedSnapSyncingSetHeadWithSnapshots(t *testing.T) {
|
||||
testShortReorgedSnapSyncingSetHead(t, true)
|
||||
}
|
||||
|
||||
func testShortReorgedFastSyncingSetHead(t *testing.T, snapshots bool) {
|
||||
func testShortReorgedSnapSyncingSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10
|
||||
@@ -829,14 +829,14 @@ func testLongDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// back to the committed block. Everything above the sethead point should be
|
||||
// deleted. In between the committed block and the requested head the data can
|
||||
// remain as "fast sync" data to avoid redownloading it.
|
||||
func TestLongFastSyncedShallowSetHead(t *testing.T) {
|
||||
testLongFastSyncedShallowSetHead(t, false)
|
||||
func TestLongSnapSyncedShallowSetHead(t *testing.T) {
|
||||
testLongSnapSyncedShallowSetHead(t, false)
|
||||
}
|
||||
func TestLongFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongFastSyncedShallowSetHead(t, true)
|
||||
func TestLongSnapSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongSnapSyncedShallowSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongSnapSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
//
|
||||
@@ -880,10 +880,10 @@ func testLongFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// which sethead was called. In this case we expect the full chain to be rolled
|
||||
// back to the committed block. Since the ancient limit was underflown, everything
|
||||
// needs to be deleted onwards to avoid creating a gap.
|
||||
func TestLongFastSyncedDeepSetHead(t *testing.T) { testLongFastSyncedDeepSetHead(t, false) }
|
||||
func TestLongFastSyncedDeepSetHeadWithSnapshots(t *testing.T) { testLongFastSyncedDeepSetHead(t, true) }
|
||||
func TestLongSnapSyncedDeepSetHead(t *testing.T) { testLongSnapSyncedDeepSetHead(t, false) }
|
||||
func TestLongSnapSyncedDeepSetHeadWithSnapshots(t *testing.T) { testLongSnapSyncedDeepSetHead(t, true) }
|
||||
|
||||
func testLongFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongSnapSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
//
|
||||
@@ -926,14 +926,14 @@ func testLongFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// sethead was called. In this case we expect the chain to detect that it was fast
|
||||
// syncing and delete everything from the new head, since we can just pick up fast
|
||||
// syncing from there.
|
||||
func TestLongFastSyncingShallowSetHead(t *testing.T) {
|
||||
testLongFastSyncingShallowSetHead(t, false)
|
||||
func TestLongSnapSyncingShallowSetHead(t *testing.T) {
|
||||
testLongSnapSyncingShallowSetHead(t, false)
|
||||
}
|
||||
func TestLongFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongFastSyncingShallowSetHead(t, true)
|
||||
func TestLongSnapSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongSnapSyncingShallowSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongSnapSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
//
|
||||
@@ -977,14 +977,14 @@ func testLongFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// sethead was called. In this case we expect the chain to detect that it was fast
|
||||
// syncing and delete everything from the new head, since we can just pick up fast
|
||||
// syncing from there.
|
||||
func TestLongFastSyncingDeepSetHead(t *testing.T) {
|
||||
testLongFastSyncingDeepSetHead(t, false)
|
||||
func TestLongSnapSyncingDeepSetHead(t *testing.T) {
|
||||
testLongSnapSyncingDeepSetHead(t, false)
|
||||
}
|
||||
func TestLongFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongFastSyncingDeepSetHead(t, true)
|
||||
func TestLongSnapSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongSnapSyncingDeepSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongSnapSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
//
|
||||
@@ -1132,14 +1132,14 @@ func testLongOldForkedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// sethead point should be deleted. In between the committed block and the
|
||||
// requested head the data can remain as "fast sync" data to avoid redownloading
|
||||
// it. The side chain is nuked by the freezer.
|
||||
func TestLongOldForkedFastSyncedShallowSetHead(t *testing.T) {
|
||||
testLongOldForkedFastSyncedShallowSetHead(t, false)
|
||||
func TestLongOldForkedSnapSyncedShallowSetHead(t *testing.T) {
|
||||
testLongOldForkedSnapSyncedShallowSetHead(t, false)
|
||||
}
|
||||
func TestLongOldForkedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedFastSyncedShallowSetHead(t, true)
|
||||
func TestLongOldForkedSnapSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedSnapSyncedShallowSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongOldForkedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongOldForkedSnapSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -1186,14 +1186,14 @@ func testLongOldForkedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// full chain to be rolled back to the committed block. Since the ancient limit was
|
||||
// underflown, everything needs to be deleted onwards to avoid creating a gap. The
|
||||
// side chain is nuked by the freezer.
|
||||
func TestLongOldForkedFastSyncedDeepSetHead(t *testing.T) {
|
||||
testLongOldForkedFastSyncedDeepSetHead(t, false)
|
||||
func TestLongOldForkedSnapSyncedDeepSetHead(t *testing.T) {
|
||||
testLongOldForkedSnapSyncedDeepSetHead(t, false)
|
||||
}
|
||||
func TestLongOldForkedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedFastSyncedDeepSetHead(t, true)
|
||||
func TestLongOldForkedSnapSyncedDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedSnapSyncedDeepSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongOldForkedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongOldForkedSnapSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -1239,14 +1239,14 @@ func testLongOldForkedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// that it was fast syncing and delete everything from the new head, since we can
|
||||
// just pick up fast syncing from there. The side chain is completely nuked by the
|
||||
// freezer.
|
||||
func TestLongOldForkedFastSyncingShallowSetHead(t *testing.T) {
|
||||
testLongOldForkedFastSyncingShallowSetHead(t, false)
|
||||
func TestLongOldForkedSnapSyncingShallowSetHead(t *testing.T) {
|
||||
testLongOldForkedSnapSyncingShallowSetHead(t, false)
|
||||
}
|
||||
func TestLongOldForkedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedFastSyncingShallowSetHead(t, true)
|
||||
func TestLongOldForkedSnapSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedSnapSyncingShallowSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongOldForkedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongOldForkedSnapSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -1293,14 +1293,14 @@ func testLongOldForkedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// that it was fast syncing and delete everything from the new head, since we can
|
||||
// just pick up fast syncing from there. The side chain is completely nuked by the
|
||||
// freezer.
|
||||
func TestLongOldForkedFastSyncingDeepSetHead(t *testing.T) {
|
||||
testLongOldForkedFastSyncingDeepSetHead(t, false)
|
||||
func TestLongOldForkedSnapSyncingDeepSetHead(t *testing.T) {
|
||||
testLongOldForkedSnapSyncingDeepSetHead(t, false)
|
||||
}
|
||||
func TestLongOldForkedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedFastSyncingDeepSetHead(t, true)
|
||||
func TestLongOldForkedSnapSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongOldForkedSnapSyncingDeepSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongOldForkedFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongOldForkedSnapSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3
|
||||
@@ -1446,15 +1446,15 @@ func testLongNewerForkedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// side chain, where the fast sync pivot point - newer than the ancient limit -
|
||||
// was already committed to disk and then sethead was called. In this test scenario
|
||||
// the side chain is above the committed block. In this case the freezer will delete
|
||||
// the sidechain since it's dangling, reverting to TestLongFastSyncedShallowSetHead.
|
||||
func TestLongNewerForkedFastSyncedShallowSetHead(t *testing.T) {
|
||||
testLongNewerForkedFastSyncedShallowSetHead(t, false)
|
||||
// the sidechain since it's dangling, reverting to TestLongSnapSyncedShallowSetHead.
|
||||
func TestLongNewerForkedSnapSyncedShallowSetHead(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncedShallowSetHead(t, false)
|
||||
}
|
||||
func TestLongNewerForkedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedFastSyncedShallowSetHead(t, true)
|
||||
func TestLongNewerForkedSnapSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncedShallowSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongNewerForkedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongNewerForkedSnapSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
@@ -1498,15 +1498,15 @@ func testLongNewerForkedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// side chain, where the fast sync pivot point - older than the ancient limit -
|
||||
// was already committed to disk and then sethead was called. In this test scenario
|
||||
// the side chain is above the committed block. In this case the freezer will delete
|
||||
// the sidechain since it's dangling, reverting to TestLongFastSyncedDeepSetHead.
|
||||
func TestLongNewerForkedFastSyncedDeepSetHead(t *testing.T) {
|
||||
testLongNewerForkedFastSyncedDeepSetHead(t, false)
|
||||
// the sidechain since it's dangling, reverting to TestLongSnapSyncedDeepSetHead.
|
||||
func TestLongNewerForkedSnapSyncedDeepSetHead(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncedDeepSetHead(t, false)
|
||||
}
|
||||
func TestLongNewerForkedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedFastSyncedDeepSetHead(t, true)
|
||||
func TestLongNewerForkedSnapSyncedDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncedDeepSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongNewerForkedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongNewerForkedSnapSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
@@ -1549,15 +1549,15 @@ func testLongNewerForkedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// side chain, where the fast sync pivot point - newer than the ancient limit -
|
||||
// was not yet committed, but sethead was called. In this test scenario the side
|
||||
// chain is above the committed block. In this case the freezer will delete the
|
||||
// sidechain since it's dangling, reverting to TestLongFastSyncinghallowSetHead.
|
||||
func TestLongNewerForkedFastSyncingShallowSetHead(t *testing.T) {
|
||||
testLongNewerForkedFastSyncingShallowSetHead(t, false)
|
||||
// sidechain since it's dangling, reverting to TestLongSnapSyncinghallowSetHead.
|
||||
func TestLongNewerForkedSnapSyncingShallowSetHead(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncingShallowSetHead(t, false)
|
||||
}
|
||||
func TestLongNewerForkedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedFastSyncingShallowSetHead(t, true)
|
||||
func TestLongNewerForkedSnapSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncingShallowSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongNewerForkedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongNewerForkedSnapSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
@@ -1601,15 +1601,15 @@ func testLongNewerForkedFastSyncingShallowSetHead(t *testing.T, snapshots bool)
|
||||
// side chain, where the fast sync pivot point - older than the ancient limit -
|
||||
// was not yet committed, but sethead was called. In this test scenario the side
|
||||
// chain is above the committed block. In this case the freezer will delete the
|
||||
// sidechain since it's dangling, reverting to TestLongFastSyncingDeepSetHead.
|
||||
func TestLongNewerForkedFastSyncingDeepSetHead(t *testing.T) {
|
||||
testLongNewerForkedFastSyncingDeepSetHead(t, false)
|
||||
// sidechain since it's dangling, reverting to TestLongSnapSyncingDeepSetHead.
|
||||
func TestLongNewerForkedSnapSyncingDeepSetHead(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncingDeepSetHead(t, false)
|
||||
}
|
||||
func TestLongNewerForkedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedFastSyncingDeepSetHead(t, true)
|
||||
func TestLongNewerForkedSnapSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongNewerForkedSnapSyncingDeepSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongNewerForkedFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongNewerForkedSnapSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12
|
||||
@@ -1745,15 +1745,15 @@ func testLongReorgedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// side chain, where the fast sync pivot point - newer than the ancient limit -
|
||||
// was already committed to disk and then sethead was called. In this case the
|
||||
// freezer will delete the sidechain since it's dangling, reverting to
|
||||
// TestLongFastSyncedShallowSetHead.
|
||||
func TestLongReorgedFastSyncedShallowSetHead(t *testing.T) {
|
||||
testLongReorgedFastSyncedShallowSetHead(t, false)
|
||||
// TestLongSnapSyncedShallowSetHead.
|
||||
func TestLongReorgedSnapSyncedShallowSetHead(t *testing.T) {
|
||||
testLongReorgedSnapSyncedShallowSetHead(t, false)
|
||||
}
|
||||
func TestLongReorgedFastSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongReorgedFastSyncedShallowSetHead(t, true)
|
||||
func TestLongReorgedSnapSyncedShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongReorgedSnapSyncedShallowSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongReorgedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongReorgedSnapSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
@@ -1797,15 +1797,15 @@ func testLongReorgedFastSyncedShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// side chain, where the fast sync pivot point - older than the ancient limit -
|
||||
// was already committed to disk and then sethead was called. In this case the
|
||||
// freezer will delete the sidechain since it's dangling, reverting to
|
||||
// TestLongFastSyncedDeepSetHead.
|
||||
func TestLongReorgedFastSyncedDeepSetHead(t *testing.T) {
|
||||
testLongReorgedFastSyncedDeepSetHead(t, false)
|
||||
// TestLongSnapSyncedDeepSetHead.
|
||||
func TestLongReorgedSnapSyncedDeepSetHead(t *testing.T) {
|
||||
testLongReorgedSnapSyncedDeepSetHead(t, false)
|
||||
}
|
||||
func TestLongReorgedFastSyncedDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongReorgedFastSyncedDeepSetHead(t, true)
|
||||
func TestLongReorgedSnapSyncedDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongReorgedSnapSyncedDeepSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongReorgedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongReorgedSnapSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
@@ -1850,14 +1850,14 @@ func testLongReorgedFastSyncedDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// chain to detect that it was fast syncing and delete everything from the new
|
||||
// head, since we can just pick up fast syncing from there. The side chain is
|
||||
// completely nuked by the freezer.
|
||||
func TestLongReorgedFastSyncingShallowSetHead(t *testing.T) {
|
||||
testLongReorgedFastSyncingShallowSetHead(t, false)
|
||||
func TestLongReorgedSnapSyncingShallowSetHead(t *testing.T) {
|
||||
testLongReorgedSnapSyncingShallowSetHead(t, false)
|
||||
}
|
||||
func TestLongReorgedFastSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongReorgedFastSyncingShallowSetHead(t, true)
|
||||
func TestLongReorgedSnapSyncingShallowSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongReorgedSnapSyncingShallowSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongReorgedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongReorgedSnapSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
@@ -1903,14 +1903,14 @@ func testLongReorgedFastSyncingShallowSetHead(t *testing.T, snapshots bool) {
|
||||
// chain to detect that it was fast syncing and delete everything from the new
|
||||
// head, since we can just pick up fast syncing from there. The side chain is
|
||||
// completely nuked by the freezer.
|
||||
func TestLongReorgedFastSyncingDeepSetHead(t *testing.T) {
|
||||
testLongReorgedFastSyncingDeepSetHead(t, false)
|
||||
func TestLongReorgedSnapSyncingDeepSetHead(t *testing.T) {
|
||||
testLongReorgedSnapSyncingDeepSetHead(t, false)
|
||||
}
|
||||
func TestLongReorgedFastSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongReorgedFastSyncingDeepSetHead(t, true)
|
||||
func TestLongReorgedSnapSyncingDeepSetHeadWithSnapshots(t *testing.T) {
|
||||
testLongReorgedSnapSyncingDeepSetHead(t, true)
|
||||
}
|
||||
|
||||
func testLongReorgedFastSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||
func testLongReorgedSnapSyncingDeepSetHead(t *testing.T, snapshots bool) {
|
||||
// Chain:
|
||||
// G->C1->C2->C3->C4->C5->C6->C7->C8->C9->C10->C11->C12->C13->C14->C15->C16->C17->C18->C19->C20->C21->C22->C23->C24 (HEAD)
|
||||
// └->S1->S2->S3->S4->S5->S6->S7->S8->S9->S10->S11->S12->S13->S14->S15->S16->S17->S18->S19->S20->S21->S22->S23->S24->S25->S26
|
||||
|
||||
+399
-31
@@ -28,13 +28,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
@@ -210,6 +213,55 @@ func TestLastBlock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test inserts the blocks/headers after the fork choice rule is changed.
|
||||
// The chain is reorged to whatever specified.
|
||||
func testInsertAfterMerge(t *testing.T, blockchain *BlockChain, i, n int, full bool) {
|
||||
// Copy old chain up to #i into a new db
|
||||
db, blockchain2, err := newCanonical(ethash.NewFaker(), i, full)
|
||||
if err != nil {
|
||||
t.Fatal("could not make new canonical in testFork", err)
|
||||
}
|
||||
defer blockchain2.Stop()
|
||||
|
||||
// Assert the chains have the same header/block at #i
|
||||
var hash1, hash2 common.Hash
|
||||
if full {
|
||||
hash1 = blockchain.GetBlockByNumber(uint64(i)).Hash()
|
||||
hash2 = blockchain2.GetBlockByNumber(uint64(i)).Hash()
|
||||
} else {
|
||||
hash1 = blockchain.GetHeaderByNumber(uint64(i)).Hash()
|
||||
hash2 = blockchain2.GetHeaderByNumber(uint64(i)).Hash()
|
||||
}
|
||||
if hash1 != hash2 {
|
||||
t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1)
|
||||
}
|
||||
|
||||
// Extend the newly created chain
|
||||
if full {
|
||||
blockChainB := makeBlockChain(blockchain2.CurrentBlock(), n, ethash.NewFaker(), db, forkSeed)
|
||||
if _, err := blockchain2.InsertChain(blockChainB); err != nil {
|
||||
t.Fatalf("failed to insert forking chain: %v", err)
|
||||
}
|
||||
if blockchain2.CurrentBlock().NumberU64() != blockChainB[len(blockChainB)-1].NumberU64() {
|
||||
t.Fatalf("failed to reorg to the given chain")
|
||||
}
|
||||
if blockchain2.CurrentBlock().Hash() != blockChainB[len(blockChainB)-1].Hash() {
|
||||
t.Fatalf("failed to reorg to the given chain")
|
||||
}
|
||||
} else {
|
||||
headerChainB := makeHeaderChain(blockchain2.CurrentHeader(), n, ethash.NewFaker(), db, forkSeed)
|
||||
if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil {
|
||||
t.Fatalf("failed to insert forking chain: %v", err)
|
||||
}
|
||||
if blockchain2.CurrentHeader().Number.Uint64() != headerChainB[len(headerChainB)-1].Number.Uint64() {
|
||||
t.Fatalf("failed to reorg to the given chain")
|
||||
}
|
||||
if blockchain2.CurrentHeader().Hash() != headerChainB[len(headerChainB)-1].Hash() {
|
||||
t.Fatalf("failed to reorg to the given chain")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that given a starting canonical chain of a given size, it can be extended
|
||||
// with various length chains.
|
||||
func TestExtendCanonicalHeaders(t *testing.T) { testExtendCanonical(t, false) }
|
||||
@@ -238,6 +290,25 @@ func testExtendCanonical(t *testing.T, full bool) {
|
||||
testFork(t, processor, length, 10, full, better)
|
||||
}
|
||||
|
||||
// Tests that given a starting canonical chain of a given size, it can be extended
|
||||
// with various length chains.
|
||||
func TestExtendCanonicalHeadersAfterMerge(t *testing.T) { testExtendCanonicalAfterMerge(t, false) }
|
||||
func TestExtendCanonicalBlocksAfterMerge(t *testing.T) { testExtendCanonicalAfterMerge(t, true) }
|
||||
|
||||
func testExtendCanonicalAfterMerge(t *testing.T, full bool) {
|
||||
length := 5
|
||||
|
||||
// Make first chain starting from genesis
|
||||
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||
}
|
||||
defer processor.Stop()
|
||||
|
||||
testInsertAfterMerge(t, processor, length, 1, full)
|
||||
testInsertAfterMerge(t, processor, length, 10, full)
|
||||
}
|
||||
|
||||
// Tests that given a starting canonical chain of a given size, creating shorter
|
||||
// forks do not take canonical ownership.
|
||||
func TestShorterForkHeaders(t *testing.T) { testShorterFork(t, false) }
|
||||
@@ -268,6 +339,29 @@ func testShorterFork(t *testing.T, full bool) {
|
||||
testFork(t, processor, 5, 4, full, worse)
|
||||
}
|
||||
|
||||
// Tests that given a starting canonical chain of a given size, creating shorter
|
||||
// forks do not take canonical ownership.
|
||||
func TestShorterForkHeadersAfterMerge(t *testing.T) { testShorterForkAfterMerge(t, false) }
|
||||
func TestShorterForkBlocksAfterMerge(t *testing.T) { testShorterForkAfterMerge(t, true) }
|
||||
|
||||
func testShorterForkAfterMerge(t *testing.T, full bool) {
|
||||
length := 10
|
||||
|
||||
// Make first chain starting from genesis
|
||||
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||
}
|
||||
defer processor.Stop()
|
||||
|
||||
testInsertAfterMerge(t, processor, 0, 3, full)
|
||||
testInsertAfterMerge(t, processor, 0, 7, full)
|
||||
testInsertAfterMerge(t, processor, 1, 1, full)
|
||||
testInsertAfterMerge(t, processor, 1, 7, full)
|
||||
testInsertAfterMerge(t, processor, 5, 3, full)
|
||||
testInsertAfterMerge(t, processor, 5, 4, full)
|
||||
}
|
||||
|
||||
// Tests that given a starting canonical chain of a given size, creating longer
|
||||
// forks do take canonical ownership.
|
||||
func TestLongerForkHeaders(t *testing.T) { testLongerFork(t, false) }
|
||||
@@ -283,19 +377,35 @@ func testLongerFork(t *testing.T, full bool) {
|
||||
}
|
||||
defer processor.Stop()
|
||||
|
||||
// Define the difficulty comparator
|
||||
better := func(td1, td2 *big.Int) {
|
||||
if td2.Cmp(td1) <= 0 {
|
||||
t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
|
||||
}
|
||||
testInsertAfterMerge(t, processor, 0, 11, full)
|
||||
testInsertAfterMerge(t, processor, 0, 15, full)
|
||||
testInsertAfterMerge(t, processor, 1, 10, full)
|
||||
testInsertAfterMerge(t, processor, 1, 12, full)
|
||||
testInsertAfterMerge(t, processor, 5, 6, full)
|
||||
testInsertAfterMerge(t, processor, 5, 8, full)
|
||||
}
|
||||
|
||||
// Tests that given a starting canonical chain of a given size, creating longer
|
||||
// forks do take canonical ownership.
|
||||
func TestLongerForkHeadersAfterMerge(t *testing.T) { testLongerForkAfterMerge(t, false) }
|
||||
func TestLongerForkBlocksAfterMerge(t *testing.T) { testLongerForkAfterMerge(t, true) }
|
||||
|
||||
func testLongerForkAfterMerge(t *testing.T, full bool) {
|
||||
length := 10
|
||||
|
||||
// Make first chain starting from genesis
|
||||
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||
}
|
||||
// Sum of numbers must be greater than `length` for this to be a longer fork
|
||||
testFork(t, processor, 0, 11, full, better)
|
||||
testFork(t, processor, 0, 15, full, better)
|
||||
testFork(t, processor, 1, 10, full, better)
|
||||
testFork(t, processor, 1, 12, full, better)
|
||||
testFork(t, processor, 5, 6, full, better)
|
||||
testFork(t, processor, 5, 8, full, better)
|
||||
defer processor.Stop()
|
||||
|
||||
testInsertAfterMerge(t, processor, 0, 11, full)
|
||||
testInsertAfterMerge(t, processor, 0, 15, full)
|
||||
testInsertAfterMerge(t, processor, 1, 10, full)
|
||||
testInsertAfterMerge(t, processor, 1, 12, full)
|
||||
testInsertAfterMerge(t, processor, 5, 6, full)
|
||||
testInsertAfterMerge(t, processor, 5, 8, full)
|
||||
}
|
||||
|
||||
// Tests that given a starting canonical chain of a given size, creating equal
|
||||
@@ -328,6 +438,29 @@ func testEqualFork(t *testing.T, full bool) {
|
||||
testFork(t, processor, 9, 1, full, equal)
|
||||
}
|
||||
|
||||
// Tests that given a starting canonical chain of a given size, creating equal
|
||||
// forks do take canonical ownership.
|
||||
func TestEqualForkHeadersAfterMerge(t *testing.T) { testEqualForkAfterMerge(t, false) }
|
||||
func TestEqualForkBlocksAfterMerge(t *testing.T) { testEqualForkAfterMerge(t, true) }
|
||||
|
||||
func testEqualForkAfterMerge(t *testing.T, full bool) {
|
||||
length := 10
|
||||
|
||||
// Make first chain starting from genesis
|
||||
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||
}
|
||||
defer processor.Stop()
|
||||
|
||||
testInsertAfterMerge(t, processor, 0, 10, full)
|
||||
testInsertAfterMerge(t, processor, 1, 9, full)
|
||||
testInsertAfterMerge(t, processor, 2, 8, full)
|
||||
testInsertAfterMerge(t, processor, 5, 5, full)
|
||||
testInsertAfterMerge(t, processor, 6, 4, full)
|
||||
testInsertAfterMerge(t, processor, 9, 1, full)
|
||||
}
|
||||
|
||||
// Tests that chains missing links do not get accepted by the processor.
|
||||
func TestBrokenHeaderChain(t *testing.T) { testBrokenChain(t, false) }
|
||||
func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) }
|
||||
@@ -1800,21 +1933,56 @@ func TestLowDiffLongChain(t *testing.T) {
|
||||
// - C is canon chain, containing blocks [G..Cn..Cm]
|
||||
// - A common ancestor is placed at prune-point + blocksBetweenCommonAncestorAndPruneblock
|
||||
// - The sidechain S is prepended with numCanonBlocksInSidechain blocks from the canon chain
|
||||
func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommonAncestorAndPruneblock int) {
|
||||
|
||||
//
|
||||
// The mergePoint can be these values:
|
||||
// -1: the transition won't happen
|
||||
// 0: the transition happens since genesis
|
||||
// 1: the transition happens after some chain segments
|
||||
func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommonAncestorAndPruneblock int, mergePoint int) {
|
||||
// Copy the TestChainConfig so we can modify it during tests
|
||||
chainConfig := *params.TestChainConfig
|
||||
// Generate a canonical chain to act as the main dataset
|
||||
engine := ethash.NewFaker()
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
genesis := (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(db)
|
||||
var (
|
||||
merger = consensus.NewMerger(rawdb.NewMemoryDatabase())
|
||||
genEngine = beacon.New(ethash.NewFaker())
|
||||
runEngine = beacon.New(ethash.NewFaker())
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||
nonce = uint64(0)
|
||||
|
||||
gspec = &Genesis{
|
||||
Config: &chainConfig,
|
||||
Alloc: GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}},
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
signer = types.LatestSigner(gspec.Config)
|
||||
genesis, _ = gspec.Commit(db)
|
||||
)
|
||||
// Generate and import the canonical chain
|
||||
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, nil)
|
||||
diskdb := rawdb.NewMemoryDatabase()
|
||||
(&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb)
|
||||
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
|
||||
gspec.MustCommit(diskdb)
|
||||
chain, err := NewBlockChain(diskdb, nil, &chainConfig, runEngine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tester chain: %v", err)
|
||||
}
|
||||
// Activate the transition since genesis if required
|
||||
if mergePoint == 0 {
|
||||
merger.ReachTTD()
|
||||
merger.FinalizePoS()
|
||||
|
||||
// Set the terminal total difficulty in the config
|
||||
gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
|
||||
}
|
||||
blocks, _ := GenerateChain(&chainConfig, genesis, genEngine, db, 2*TriesInMemory, func(i int, gen *BlockGen) {
|
||||
tx, err := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("deadbeef"), big.NewInt(100), 21000, big.NewInt(int64(i+1)*params.GWei), nil), signer, key)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tx: %v", err)
|
||||
}
|
||||
gen.AddTx(tx)
|
||||
nonce++
|
||||
})
|
||||
if n, err := chain.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
|
||||
}
|
||||
@@ -1831,6 +1999,15 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
|
||||
if !chain.HasBlockAndState(firstNonPrunedBlock.Hash(), firstNonPrunedBlock.NumberU64()) {
|
||||
t.Errorf("Block %d pruned", firstNonPrunedBlock.NumberU64())
|
||||
}
|
||||
|
||||
// Activate the transition in the middle of the chain
|
||||
if mergePoint == 1 {
|
||||
merger.ReachTTD()
|
||||
merger.FinalizePoS()
|
||||
// Set the terminal total difficulty in the config
|
||||
gspec.Config.TerminalTotalDifficulty = big.NewInt(int64(len(blocks)))
|
||||
}
|
||||
|
||||
// Generate the sidechain
|
||||
// First block should be a known block, block after should be a pruned block. So
|
||||
// canon(pruned), side, side...
|
||||
@@ -1838,7 +2015,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
|
||||
// Generate fork chain, make it longer than canon
|
||||
parentIndex := lastPrunedIndex + blocksBetweenCommonAncestorAndPruneblock
|
||||
parent := blocks[parentIndex]
|
||||
fork, _ := GenerateChain(params.TestChainConfig, parent, engine, db, 2*TriesInMemory, func(i int, b *BlockGen) {
|
||||
fork, _ := GenerateChain(&chainConfig, parent, genEngine, db, 2*TriesInMemory, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{2})
|
||||
})
|
||||
// Prepend the parent(s)
|
||||
@@ -1847,9 +2024,9 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
|
||||
sidechain = append(sidechain, blocks[parentIndex+1-i])
|
||||
}
|
||||
sidechain = append(sidechain, fork...)
|
||||
_, err = chain.InsertChain(sidechain)
|
||||
n, err := chain.InsertChain(sidechain)
|
||||
if err != nil {
|
||||
t.Errorf("Got error, %v", err)
|
||||
t.Errorf("Got error, %v number %d - %d", err, sidechain[n].NumberU64(), n)
|
||||
}
|
||||
head := chain.CurrentBlock()
|
||||
if got := fork[len(fork)-1].Hash(); got != head.Hash() {
|
||||
@@ -1870,11 +2047,28 @@ func TestPrunedImportSide(t *testing.T) {
|
||||
//glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false)))
|
||||
//glogger.Verbosity(3)
|
||||
//log.Root().SetHandler(log.Handler(glogger))
|
||||
testSideImport(t, 3, 3)
|
||||
testSideImport(t, 3, -3)
|
||||
testSideImport(t, 10, 0)
|
||||
testSideImport(t, 1, 10)
|
||||
testSideImport(t, 1, -10)
|
||||
testSideImport(t, 3, 3, -1)
|
||||
testSideImport(t, 3, -3, -1)
|
||||
testSideImport(t, 10, 0, -1)
|
||||
testSideImport(t, 1, 10, -1)
|
||||
testSideImport(t, 1, -10, -1)
|
||||
}
|
||||
|
||||
func TestPrunedImportSideWithMerging(t *testing.T) {
|
||||
//glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false)))
|
||||
//glogger.Verbosity(3)
|
||||
//log.Root().SetHandler(log.Handler(glogger))
|
||||
testSideImport(t, 3, 3, 0)
|
||||
testSideImport(t, 3, -3, 0)
|
||||
testSideImport(t, 10, 0, 0)
|
||||
testSideImport(t, 1, 10, 0)
|
||||
testSideImport(t, 1, -10, 0)
|
||||
|
||||
testSideImport(t, 3, 3, 1)
|
||||
testSideImport(t, 3, -3, 1)
|
||||
testSideImport(t, 10, 0, 1)
|
||||
testSideImport(t, 1, 10, 1)
|
||||
testSideImport(t, 1, -10, 1)
|
||||
}
|
||||
|
||||
func TestInsertKnownHeaders(t *testing.T) { testInsertKnownChainData(t, "headers") }
|
||||
@@ -2002,6 +2196,179 @@ func testInsertKnownChainData(t *testing.T, typ string) {
|
||||
asserter(t, blocks2[len(blocks2)-1])
|
||||
}
|
||||
|
||||
func TestInsertKnownHeadersWithMerging(t *testing.T) {
|
||||
testInsertKnownChainDataWithMerging(t, "headers", 0)
|
||||
}
|
||||
func TestInsertKnownReceiptChainWithMerging(t *testing.T) {
|
||||
testInsertKnownChainDataWithMerging(t, "receipts", 0)
|
||||
}
|
||||
func TestInsertKnownBlocksWithMerging(t *testing.T) {
|
||||
testInsertKnownChainDataWithMerging(t, "blocks", 0)
|
||||
}
|
||||
func TestInsertKnownHeadersAfterMerging(t *testing.T) {
|
||||
testInsertKnownChainDataWithMerging(t, "headers", 1)
|
||||
}
|
||||
func TestInsertKnownReceiptChainAfterMerging(t *testing.T) {
|
||||
testInsertKnownChainDataWithMerging(t, "receipts", 1)
|
||||
}
|
||||
func TestInsertKnownBlocksAfterMerging(t *testing.T) {
|
||||
testInsertKnownChainDataWithMerging(t, "blocks", 1)
|
||||
}
|
||||
|
||||
// mergeHeight can be assigned in these values:
|
||||
// 0: means the merging is applied since genesis
|
||||
// 1: means the merging is applied after the first segment
|
||||
func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight int) {
|
||||
// Copy the TestChainConfig so we can modify it during tests
|
||||
chainConfig := *params.TestChainConfig
|
||||
var (
|
||||
db = rawdb.NewMemoryDatabase()
|
||||
genesis = (&Genesis{BaseFee: big.NewInt(params.InitialBaseFee), Config: &chainConfig}).MustCommit(db)
|
||||
runMerger = consensus.NewMerger(db)
|
||||
runEngine = beacon.New(ethash.NewFaker())
|
||||
genEngine = beacon.New(ethash.NewFaker())
|
||||
)
|
||||
applyMerge := func(engine *beacon.Beacon, height int) {
|
||||
if engine != nil {
|
||||
runMerger.FinalizePoS()
|
||||
// Set the terminal total difficulty in the config
|
||||
chainConfig.TerminalTotalDifficulty = big.NewInt(int64(height))
|
||||
}
|
||||
}
|
||||
|
||||
// Apply merging since genesis
|
||||
if mergeHeight == 0 {
|
||||
applyMerge(genEngine, 0)
|
||||
}
|
||||
blocks, receipts := GenerateChain(&chainConfig, genesis, genEngine, db, 32, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
|
||||
|
||||
// Apply merging after the first segment
|
||||
if mergeHeight == 1 {
|
||||
applyMerge(genEngine, len(blocks))
|
||||
}
|
||||
// Longer chain and shorter chain
|
||||
blocks2, receipts2 := GenerateChain(&chainConfig, blocks[len(blocks)-1], genEngine, db, 65, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
|
||||
blocks3, receipts3 := GenerateChain(&chainConfig, blocks[len(blocks)-1], genEngine, db, 64, func(i int, b *BlockGen) {
|
||||
b.SetCoinbase(common.Address{1})
|
||||
b.OffsetTime(-9) // Time shifted, difficulty shouldn't be changed
|
||||
})
|
||||
|
||||
// Import the shared chain and the original canonical one
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp freezer dir: %v", err)
|
||||
}
|
||||
defer os.Remove(dir)
|
||||
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), dir, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp freezer db: %v", err)
|
||||
}
|
||||
(&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(chaindb)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
chain, err := NewBlockChain(chaindb, nil, &chainConfig, runEngine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tester chain: %v", err)
|
||||
}
|
||||
var (
|
||||
inserter func(blocks []*types.Block, receipts []types.Receipts) error
|
||||
asserter func(t *testing.T, block *types.Block)
|
||||
)
|
||||
if typ == "headers" {
|
||||
inserter = func(blocks []*types.Block, receipts []types.Receipts) error {
|
||||
headers := make([]*types.Header, 0, len(blocks))
|
||||
for _, block := range blocks {
|
||||
headers = append(headers, block.Header())
|
||||
}
|
||||
_, err := chain.InsertHeaderChain(headers, 1)
|
||||
return err
|
||||
}
|
||||
asserter = func(t *testing.T, block *types.Block) {
|
||||
if chain.CurrentHeader().Hash() != block.Hash() {
|
||||
t.Fatalf("current head header mismatch, have %v, want %v", chain.CurrentHeader().Hash().Hex(), block.Hash().Hex())
|
||||
}
|
||||
}
|
||||
} else if typ == "receipts" {
|
||||
inserter = func(blocks []*types.Block, receipts []types.Receipts) error {
|
||||
headers := make([]*types.Header, 0, len(blocks))
|
||||
for _, block := range blocks {
|
||||
headers = append(headers, block.Header())
|
||||
}
|
||||
_, err := chain.InsertHeaderChain(headers, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = chain.InsertReceiptChain(blocks, receipts, 0)
|
||||
return err
|
||||
}
|
||||
asserter = func(t *testing.T, block *types.Block) {
|
||||
if chain.CurrentFastBlock().Hash() != block.Hash() {
|
||||
t.Fatalf("current head fast block mismatch, have %v, want %v", chain.CurrentFastBlock().Hash().Hex(), block.Hash().Hex())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inserter = func(blocks []*types.Block, receipts []types.Receipts) error {
|
||||
_, err := chain.InsertChain(blocks)
|
||||
return err
|
||||
}
|
||||
asserter = func(t *testing.T, block *types.Block) {
|
||||
if chain.CurrentBlock().Hash() != block.Hash() {
|
||||
t.Fatalf("current head block mismatch, have %v, want %v", chain.CurrentBlock().Hash().Hex(), block.Hash().Hex())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply merging since genesis if required
|
||||
if mergeHeight == 0 {
|
||||
applyMerge(runEngine, 0)
|
||||
}
|
||||
if err := inserter(blocks, receipts); err != nil {
|
||||
t.Fatalf("failed to insert chain data: %v", err)
|
||||
}
|
||||
|
||||
// Reimport the chain data again. All the imported
|
||||
// chain data are regarded "known" data.
|
||||
if err := inserter(blocks, receipts); err != nil {
|
||||
t.Fatalf("failed to insert chain data: %v", err)
|
||||
}
|
||||
asserter(t, blocks[len(blocks)-1])
|
||||
|
||||
// Import a long canonical chain with some known data as prefix.
|
||||
rollback := blocks[len(blocks)/2].NumberU64()
|
||||
chain.SetHead(rollback - 1)
|
||||
if err := inserter(blocks, receipts); err != nil {
|
||||
t.Fatalf("failed to insert chain data: %v", err)
|
||||
}
|
||||
asserter(t, blocks[len(blocks)-1])
|
||||
|
||||
// Apply merging after the first segment
|
||||
if mergeHeight == 1 {
|
||||
applyMerge(runEngine, len(blocks))
|
||||
}
|
||||
|
||||
// Import a longer chain with some known data as prefix.
|
||||
if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil {
|
||||
t.Fatalf("failed to insert chain data: %v", err)
|
||||
}
|
||||
asserter(t, blocks2[len(blocks2)-1])
|
||||
|
||||
// Import a shorter chain with some known data as prefix.
|
||||
// The reorg is expected since the fork choice rule is
|
||||
// already changed.
|
||||
if err := inserter(append(blocks, blocks3...), append(receipts, receipts3...)); err != nil {
|
||||
t.Fatalf("failed to insert chain data: %v", err)
|
||||
}
|
||||
// The head shouldn't change.
|
||||
asserter(t, blocks3[len(blocks3)-1])
|
||||
|
||||
// Reimport the longer chain again, the reorg is still expected
|
||||
chain.SetHead(rollback - 1)
|
||||
if err := inserter(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil {
|
||||
t.Fatalf("failed to insert chain data: %v", err)
|
||||
}
|
||||
asserter(t, blocks2[len(blocks2)-1])
|
||||
}
|
||||
|
||||
// getLongAndShortChains returns two chains: A is longer, B is heavier.
|
||||
func getLongAndShortChains() (bc *BlockChain, longChain []*types.Block, heavyChain []*types.Block, err error) {
|
||||
// Generate a canonical chain to act as the main dataset
|
||||
@@ -2270,7 +2637,7 @@ func TestTransactionIndices(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipStaleTxIndicesInFastSync(t *testing.T) {
|
||||
func TestSkipStaleTxIndicesInSnapSync(t *testing.T) {
|
||||
// Configure and generate a sample block chain
|
||||
var (
|
||||
gendb = rawdb.NewMemoryDatabase()
|
||||
@@ -2482,6 +2849,7 @@ func TestSideImportPrunedBlocks(t *testing.T) {
|
||||
// Generate and import the canonical chain
|
||||
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*TriesInMemory, nil)
|
||||
diskdb := rawdb.NewMemoryDatabase()
|
||||
|
||||
(&Genesis{BaseFee: big.NewInt(params.InitialBaseFee)}).MustCommit(diskdb)
|
||||
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
@@ -2690,7 +3058,7 @@ func TestDeleteRecreateSlots(t *testing.T) {
|
||||
gspec.MustCommit(diskdb)
|
||||
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{
|
||||
Debug: true,
|
||||
Tracer: vm.NewJSONLogger(nil, os.Stdout),
|
||||
Tracer: logger.NewJSONLogger(nil, os.Stdout),
|
||||
}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tester chain: %v", err)
|
||||
@@ -2770,7 +3138,7 @@ func TestDeleteRecreateAccount(t *testing.T) {
|
||||
gspec.MustCommit(diskdb)
|
||||
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{
|
||||
Debug: true,
|
||||
Tracer: vm.NewJSONLogger(nil, os.Stdout),
|
||||
Tracer: logger.NewJSONLogger(nil, os.Stdout),
|
||||
}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create tester chain: %v", err)
|
||||
|
||||
@@ -155,6 +155,28 @@ func (b *BlockGen) TxNonce(addr common.Address) uint64 {
|
||||
|
||||
// AddUncle adds an uncle header to the generated block.
|
||||
func (b *BlockGen) AddUncle(h *types.Header) {
|
||||
// The uncle will have the same timestamp and auto-generated difficulty
|
||||
h.Time = b.header.Time
|
||||
|
||||
var parent *types.Header
|
||||
for i := b.i - 1; i >= 0; i-- {
|
||||
if b.chain[i].Hash() == h.ParentHash {
|
||||
parent = b.chain[i].Header()
|
||||
break
|
||||
}
|
||||
}
|
||||
chainreader := &fakeChainReader{config: b.config}
|
||||
h.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, parent)
|
||||
|
||||
// The gas limit and price should be derived from the parent
|
||||
h.GasLimit = parent.GasLimit
|
||||
if b.config.IsLondon(h.Number) {
|
||||
h.BaseFee = misc.CalcBaseFee(b.config, parent)
|
||||
if !b.config.IsLondon(parent.Number) {
|
||||
parentGasLimit := parent.GasLimit * params.ElasticityMultiplier
|
||||
h.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit)
|
||||
}
|
||||
}
|
||||
b.uncles = append(b.uncles, h)
|
||||
}
|
||||
|
||||
@@ -205,6 +227,18 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||
b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine}
|
||||
b.header = makeHeader(chainreader, parent, statedb, b.engine)
|
||||
|
||||
// Set the difficulty for clique block. The chain maker doesn't have access
|
||||
// to a chain, so the difficulty will be left unset (nil). Set it here to the
|
||||
// correct value.
|
||||
if b.header.Difficulty == nil {
|
||||
if config.TerminalTotalDifficulty == nil {
|
||||
// Clique chain
|
||||
b.header.Difficulty = big.NewInt(2)
|
||||
} else {
|
||||
// Post-merge chain
|
||||
b.header.Difficulty = big.NewInt(0)
|
||||
}
|
||||
}
|
||||
// Mutate the state and block according to any hard-fork specs
|
||||
if daoBlock := config.DAOForkBlock; daoBlock != nil {
|
||||
limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
|
||||
@@ -313,3 +347,4 @@ func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header
|
||||
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil }
|
||||
func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil }
|
||||
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }
|
||||
func (cr *fakeChainReader) GetTd(hash common.Hash, number uint64) *big.Int { return nil }
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2021 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 core
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// ChainReader defines a small collection of methods needed to access the local
|
||||
// blockchain during header verification. It's implemented by both blockchain
|
||||
// and lightchain.
|
||||
type ChainReader interface {
|
||||
// Config retrieves the header chain's chain configuration.
|
||||
Config() *params.ChainConfig
|
||||
|
||||
// GetTd returns the total difficulty of a local block.
|
||||
GetTd(common.Hash, uint64) *big.Int
|
||||
}
|
||||
|
||||
// ForkChoice is the fork chooser based on the highest total difficulty of the
|
||||
// chain(the fork choice used in the eth1) and the external fork choice (the fork
|
||||
// choice used in the eth2). This main goal of this ForkChoice is not only for
|
||||
// offering fork choice during the eth1/2 merge phase, but also keep the compatibility
|
||||
// for all other proof-of-work networks.
|
||||
type ForkChoice struct {
|
||||
chain ChainReader
|
||||
rand *mrand.Rand
|
||||
|
||||
// preserve is a helper function used in td fork choice.
|
||||
// Miners will prefer to choose the local mined block if the
|
||||
// local td is equal to the extern one. It can be nil for light
|
||||
// client
|
||||
preserve func(header *types.Header) bool
|
||||
}
|
||||
|
||||
func NewForkChoice(chainReader ChainReader, preserve func(header *types.Header) bool) *ForkChoice {
|
||||
// Seed a fast but crypto originating random generator
|
||||
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
|
||||
if err != nil {
|
||||
log.Crit("Failed to initialize random seed", "err", err)
|
||||
}
|
||||
return &ForkChoice{
|
||||
chain: chainReader,
|
||||
rand: mrand.New(mrand.NewSource(seed.Int64())),
|
||||
preserve: preserve,
|
||||
}
|
||||
}
|
||||
|
||||
// ReorgNeeded returns whether the reorg should be applied
|
||||
// based on the given external header and local canonical chain.
|
||||
// In the td mode, the new head is chosen if the corresponding
|
||||
// total difficulty is higher. In the extern mode, the trusted
|
||||
// header is always selected as the head.
|
||||
func (f *ForkChoice) ReorgNeeded(current *types.Header, header *types.Header) (bool, error) {
|
||||
var (
|
||||
localTD = f.chain.GetTd(current.Hash(), current.Number.Uint64())
|
||||
externTd = f.chain.GetTd(header.Hash(), header.Number.Uint64())
|
||||
)
|
||||
if localTD == nil || externTd == nil {
|
||||
return false, errors.New("missing td")
|
||||
}
|
||||
// Accept the new header as the chain head if the transition
|
||||
// is already triggered. We assume all the headers after the
|
||||
// transition come from the trusted consensus layer.
|
||||
if ttd := f.chain.Config().TerminalTotalDifficulty; ttd != nil && ttd.Cmp(externTd) <= 0 {
|
||||
return true, nil
|
||||
}
|
||||
// If the total difficulty is higher than our known, add it to the canonical chain
|
||||
// Second clause in the if statement reduces the vulnerability to selfish mining.
|
||||
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
|
||||
reorg := externTd.Cmp(localTD) > 0
|
||||
if !reorg && externTd.Cmp(localTD) == 0 {
|
||||
number, headNumber := header.Number.Uint64(), current.Number.Uint64()
|
||||
if number < headNumber {
|
||||
reorg = true
|
||||
} else if number == headNumber {
|
||||
var currentPreserve, externPreserve bool
|
||||
if f.preserve != nil {
|
||||
currentPreserve, externPreserve = f.preserve(current), f.preserve(header)
|
||||
}
|
||||
reorg = !currentPreserve && (externPreserve || f.rand.Float64() < 0.5)
|
||||
}
|
||||
}
|
||||
return reorg, nil
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package forkid
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -29,6 +30,8 @@ import (
|
||||
// TestCreation tests that different genesis and fork rule combinations result in
|
||||
// the correct fork ID.
|
||||
func TestCreation(t *testing.T) {
|
||||
mergeConfig := *params.MainnetChainConfig
|
||||
mergeConfig.MergeForkBlock = big.NewInt(15000000)
|
||||
type testcase struct {
|
||||
head uint64
|
||||
want ID
|
||||
@@ -65,7 +68,7 @@ func TestCreation(t *testing.T) {
|
||||
{12964999, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block
|
||||
{12965000, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // First London block
|
||||
{13772999, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // Last London block
|
||||
{13773000, ID{Hash: checksumToBytes(0x20c327fc), Next: 0}}, /// First Arrow Glacier block
|
||||
{13773000, ID{Hash: checksumToBytes(0x20c327fc), Next: 0}}, // First Arrow Glacier block
|
||||
{20000000, ID{Hash: checksumToBytes(0x20c327fc), Next: 0}}, // Future Arrow Glacier block
|
||||
},
|
||||
},
|
||||
@@ -133,6 +136,38 @@ func TestCreation(t *testing.T) {
|
||||
{6000000, ID{Hash: checksumToBytes(0xB8C6299D), Next: 0}}, // Future London block
|
||||
},
|
||||
},
|
||||
// Merge test cases
|
||||
{
|
||||
&mergeConfig,
|
||||
params.MainnetGenesisHash,
|
||||
[]testcase{
|
||||
{0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced
|
||||
{1149999, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block
|
||||
{1150000, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // First Homestead block
|
||||
{1919999, ID{Hash: checksumToBytes(0x97c2c34c), Next: 1920000}}, // Last Homestead block
|
||||
{1920000, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // First DAO block
|
||||
{2462999, ID{Hash: checksumToBytes(0x91d1f948), Next: 2463000}}, // Last DAO block
|
||||
{2463000, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // First Tangerine block
|
||||
{2674999, ID{Hash: checksumToBytes(0x7a64da13), Next: 2675000}}, // Last Tangerine block
|
||||
{2675000, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // First Spurious block
|
||||
{4369999, ID{Hash: checksumToBytes(0x3edd5b10), Next: 4370000}}, // Last Spurious block
|
||||
{4370000, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // First Byzantium block
|
||||
{7279999, ID{Hash: checksumToBytes(0xa00bc324), Next: 7280000}}, // Last Byzantium block
|
||||
{7280000, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // First and last Constantinople, first Petersburg block
|
||||
{9068999, ID{Hash: checksumToBytes(0x668db0af), Next: 9069000}}, // Last Petersburg block
|
||||
{9069000, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // First Istanbul and first Muir Glacier block
|
||||
{9199999, ID{Hash: checksumToBytes(0x879d6e30), Next: 9200000}}, // Last Istanbul and first Muir Glacier block
|
||||
{9200000, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // First Muir Glacier block
|
||||
{12243999, ID{Hash: checksumToBytes(0xe029e991), Next: 12244000}}, // Last Muir Glacier block
|
||||
{12244000, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // First Berlin block
|
||||
{12964999, ID{Hash: checksumToBytes(0x0eb440f6), Next: 12965000}}, // Last Berlin block
|
||||
{12965000, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // First London block
|
||||
{13772999, ID{Hash: checksumToBytes(0xb715077d), Next: 13773000}}, // Last London block
|
||||
{13773000, ID{Hash: checksumToBytes(0x20c327fc), Next: 15000000}}, // First Arrow Glacier block
|
||||
{15000000, ID{Hash: checksumToBytes(0xe3abe201), Next: 0}}, // First Merge Start block
|
||||
{20000000, ID{Hash: checksumToBytes(0xe3abe201), Next: 0}}, // Future Merge Start block
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
for j, ttt := range tt.cases {
|
||||
|
||||
+5
-2
@@ -155,10 +155,10 @@ func (e *GenesisMismatchError) Error() string {
|
||||
//
|
||||
// The returned chain configuration is never nil.
|
||||
func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig, common.Hash, error) {
|
||||
return SetupGenesisBlockWithOverride(db, genesis, nil)
|
||||
return SetupGenesisBlockWithOverride(db, genesis, nil, nil)
|
||||
}
|
||||
|
||||
func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideArrowGlacier *big.Int) (*params.ChainConfig, common.Hash, error) {
|
||||
func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideArrowGlacier, overrideTerminalTotalDifficulty *big.Int) (*params.ChainConfig, common.Hash, error) {
|
||||
if genesis != nil && genesis.Config == nil {
|
||||
return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig
|
||||
}
|
||||
@@ -207,6 +207,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
|
||||
if overrideArrowGlacier != nil {
|
||||
newcfg.ArrowGlacierBlock = overrideArrowGlacier
|
||||
}
|
||||
if overrideTerminalTotalDifficulty != nil {
|
||||
newcfg.TerminalTotalDifficulty = overrideTerminalTotalDifficulty
|
||||
}
|
||||
if err := newcfg.CheckConfigForkOrder(); err != nil {
|
||||
return newcfg, common.Hash{}, err
|
||||
}
|
||||
|
||||
+170
-126
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
@@ -49,15 +50,14 @@ const (
|
||||
// HeaderChain is responsible for maintaining the header chain including the
|
||||
// header query and updating.
|
||||
//
|
||||
// The components maintained by headerchain includes: (1) total difficult
|
||||
// The components maintained by headerchain includes: (1) total difficulty
|
||||
// (2) header (3) block hash -> number mapping (4) canonical number -> hash mapping
|
||||
// and (5) head header flag.
|
||||
//
|
||||
// It is not thread safe either, the encapsulating chain structures should do
|
||||
// the necessary mutex locking/unlocking.
|
||||
type HeaderChain struct {
|
||||
config *params.ChainConfig
|
||||
|
||||
config *params.ChainConfig
|
||||
chainDb ethdb.Database
|
||||
genesisHeader *types.Header
|
||||
|
||||
@@ -86,7 +86,6 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hc := &HeaderChain{
|
||||
config: config,
|
||||
chainDb: chainDb,
|
||||
@@ -97,12 +96,10 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
||||
rand: mrand.New(mrand.NewSource(seed.Int64())),
|
||||
engine: engine,
|
||||
}
|
||||
|
||||
hc.genesisHeader = hc.GetHeaderByNumber(0)
|
||||
if hc.genesisHeader == nil {
|
||||
return nil, ErrNoGenesis
|
||||
}
|
||||
|
||||
hc.currentHeader.Store(hc.genesisHeader)
|
||||
if head := rawdb.ReadHeadBlockHash(chainDb); head != (common.Hash{}) {
|
||||
if chead := hc.GetHeaderByHash(head); chead != nil {
|
||||
@@ -111,7 +108,6 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
||||
}
|
||||
hc.currentHeaderHash = hc.CurrentHeader().Hash()
|
||||
headHeaderGauge.Update(hc.CurrentHeader().Number.Int64())
|
||||
|
||||
return hc, nil
|
||||
}
|
||||
|
||||
@@ -137,35 +133,93 @@ type headerWriteResult struct {
|
||||
lastHeader *types.Header
|
||||
}
|
||||
|
||||
// WriteHeaders writes a chain of headers into the local chain, given that the parents
|
||||
// are already known. If the total difficulty of the newly inserted chain becomes
|
||||
// greater than the current known TD, the canonical chain is reorged.
|
||||
//
|
||||
// Note: This method is not concurrent-safe with inserting blocks simultaneously
|
||||
// into the chain, as side effects caused by reorganisations cannot be emulated
|
||||
// without the real blocks. Hence, writing headers directly should only be done
|
||||
// in two scenarios: pure-header mode of operation (light clients), or properly
|
||||
// separated header/block phases (non-archive clients).
|
||||
func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWriteResult, err error) {
|
||||
// Reorg reorgs the local canonical chain into the specified chain. The reorg
|
||||
// can be classified into two cases: (a) extend the local chain (b) switch the
|
||||
// head to the given header.
|
||||
func (hc *HeaderChain) Reorg(headers []*types.Header) error {
|
||||
// Short circuit if nothing to reorg.
|
||||
if len(headers) == 0 {
|
||||
return &headerWriteResult{}, nil
|
||||
return nil
|
||||
}
|
||||
// If the parent of the (first) block is already the canon header,
|
||||
// we don't have to go backwards to delete canon blocks, but simply
|
||||
// pile them onto the existing chain. Otherwise, do the necessary
|
||||
// reorgs.
|
||||
var (
|
||||
first = headers[0]
|
||||
last = headers[len(headers)-1]
|
||||
batch = hc.chainDb.NewBatch()
|
||||
)
|
||||
if first.ParentHash != hc.currentHeaderHash {
|
||||
// Delete any canonical number assignments above the new head
|
||||
for i := last.Number.Uint64() + 1; ; i++ {
|
||||
hash := rawdb.ReadCanonicalHash(hc.chainDb, i)
|
||||
if hash == (common.Hash{}) {
|
||||
break
|
||||
}
|
||||
rawdb.DeleteCanonicalHash(batch, i)
|
||||
}
|
||||
// Overwrite any stale canonical number assignments, going
|
||||
// backwards from the first header in this import until the
|
||||
// cross link between two chains.
|
||||
var (
|
||||
header = first
|
||||
headNumber = header.Number.Uint64()
|
||||
headHash = header.Hash()
|
||||
)
|
||||
for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash {
|
||||
rawdb.WriteCanonicalHash(batch, headHash, headNumber)
|
||||
if headNumber == 0 {
|
||||
break // It shouldn't be reached
|
||||
}
|
||||
headHash, headNumber = header.ParentHash, header.Number.Uint64()-1
|
||||
header = hc.GetHeader(headHash, headNumber)
|
||||
if header == nil {
|
||||
return fmt.Errorf("missing parent %d %x", headNumber, headHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Extend the canonical chain with the new headers
|
||||
for i := 0; i < len(headers)-1; i++ {
|
||||
hash := headers[i+1].ParentHash // Save some extra hashing
|
||||
num := headers[i].Number.Uint64()
|
||||
rawdb.WriteCanonicalHash(batch, hash, num)
|
||||
rawdb.WriteHeadHeaderHash(batch, hash)
|
||||
}
|
||||
// Write the last header
|
||||
hash := headers[len(headers)-1].Hash()
|
||||
num := headers[len(headers)-1].Number.Uint64()
|
||||
rawdb.WriteCanonicalHash(batch, hash, num)
|
||||
rawdb.WriteHeadHeaderHash(batch, hash)
|
||||
|
||||
if err := batch.Write(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Last step update all in-memory head header markers
|
||||
hc.currentHeaderHash = last.Hash()
|
||||
hc.currentHeader.Store(types.CopyHeader(last))
|
||||
headHeaderGauge.Update(last.Number.Int64())
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHeaders writes a chain of headers into the local chain, given that the
|
||||
// parents are already known. The chain head header won't be updated in this
|
||||
// function, the additional setChainHead is expected in order to finish the entire
|
||||
// procedure.
|
||||
func (hc *HeaderChain) WriteHeaders(headers []*types.Header) (int, error) {
|
||||
if len(headers) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
ptd := hc.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1)
|
||||
if ptd == nil {
|
||||
return &headerWriteResult{}, consensus.ErrUnknownAncestor
|
||||
return 0, consensus.ErrUnknownAncestor
|
||||
}
|
||||
var (
|
||||
lastNumber = headers[0].Number.Uint64() - 1 // Last successfully imported number
|
||||
lastHash = headers[0].ParentHash // Last imported header hash
|
||||
newTD = new(big.Int).Set(ptd) // Total difficulty of inserted chain
|
||||
|
||||
lastHeader *types.Header
|
||||
inserted []numberHash // Ephemeral lookup of number/hash for the chain
|
||||
firstInserted = -1 // Index of the first non-ignored header
|
||||
newTD = new(big.Int).Set(ptd) // Total difficulty of inserted chain
|
||||
inserted []rawdb.NumberHash // Ephemeral lookup of number/hash for the chain
|
||||
parentKnown = true // Set to true to force hc.HasHeader check the first iteration
|
||||
batch = hc.chainDb.NewBatch()
|
||||
)
|
||||
|
||||
batch := hc.chainDb.NewBatch()
|
||||
parentKnown := true // Set to true to force hc.HasHeader check the first iteration
|
||||
for i, header := range headers {
|
||||
var hash common.Hash
|
||||
// The headers have already been validated at this point, so we already
|
||||
@@ -188,116 +242,67 @@ func (hc *HeaderChain) writeHeaders(headers []*types.Header) (result *headerWrit
|
||||
hc.tdCache.Add(hash, new(big.Int).Set(newTD))
|
||||
|
||||
rawdb.WriteHeader(batch, header)
|
||||
inserted = append(inserted, numberHash{number, hash})
|
||||
inserted = append(inserted, rawdb.NumberHash{Number: number, Hash: hash})
|
||||
hc.headerCache.Add(hash, header)
|
||||
hc.numberCache.Add(hash, number)
|
||||
if firstInserted < 0 {
|
||||
firstInserted = i
|
||||
}
|
||||
}
|
||||
parentKnown = alreadyKnown
|
||||
lastHeader, lastHash, lastNumber = header, hash, number
|
||||
}
|
||||
|
||||
// Skip the slow disk write of all headers if interrupted.
|
||||
if hc.procInterrupt() {
|
||||
log.Debug("Premature abort during headers import")
|
||||
return &headerWriteResult{}, errors.New("aborted")
|
||||
return 0, errors.New("aborted")
|
||||
}
|
||||
// Commit to disk!
|
||||
if err := batch.Write(); err != nil {
|
||||
log.Crit("Failed to write headers", "error", err)
|
||||
}
|
||||
batch.Reset()
|
||||
return len(inserted), nil
|
||||
}
|
||||
|
||||
// writeHeadersAndSetHead writes a batch of block headers and applies the last
|
||||
// header as the chain head if the fork choicer says it's ok to update the chain.
|
||||
// Note: This method is not concurrent-safe with inserting blocks simultaneously
|
||||
// into the chain, as side effects caused by reorganisations cannot be emulated
|
||||
// without the real blocks. Hence, writing headers directly should only be done
|
||||
// in two scenarios: pure-header mode of operation (light clients), or properly
|
||||
// separated header/block phases (non-archive clients).
|
||||
func (hc *HeaderChain) writeHeadersAndSetHead(headers []*types.Header, forker *ForkChoice) (*headerWriteResult, error) {
|
||||
inserted, err := hc.WriteHeaders(headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
head = hc.CurrentHeader().Number.Uint64()
|
||||
localTD = hc.GetTd(hc.currentHeaderHash, head)
|
||||
status = SideStatTy
|
||||
lastHeader = headers[len(headers)-1]
|
||||
lastHash = headers[len(headers)-1].Hash()
|
||||
result = &headerWriteResult{
|
||||
status: NonStatTy,
|
||||
ignored: len(headers) - inserted,
|
||||
imported: inserted,
|
||||
lastHash: lastHash,
|
||||
lastHeader: lastHeader,
|
||||
}
|
||||
)
|
||||
// If the total difficulty is higher than our known, add it to the canonical chain
|
||||
// Second clause in the if statement reduces the vulnerability to selfish mining.
|
||||
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
|
||||
reorg := newTD.Cmp(localTD) > 0
|
||||
if !reorg && newTD.Cmp(localTD) == 0 {
|
||||
if lastNumber < head {
|
||||
reorg = true
|
||||
} else if lastNumber == head {
|
||||
reorg = mrand.Float64() < 0.5
|
||||
// Ask the fork choicer if the reorg is necessary
|
||||
if reorg, err := forker.ReorgNeeded(hc.CurrentHeader(), lastHeader); err != nil {
|
||||
return nil, err
|
||||
} else if !reorg {
|
||||
if inserted != 0 {
|
||||
result.status = SideStatTy
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
// If the parent of the (first) block is already the canon header,
|
||||
// we don't have to go backwards to delete canon blocks, but
|
||||
// simply pile them onto the existing chain
|
||||
chainAlreadyCanon := headers[0].ParentHash == hc.currentHeaderHash
|
||||
if reorg {
|
||||
// If the header can be added into canonical chain, adjust the
|
||||
// header chain markers(canonical indexes and head header flag).
|
||||
//
|
||||
// Note all markers should be written atomically.
|
||||
markerBatch := batch // we can reuse the batch to keep allocs down
|
||||
if !chainAlreadyCanon {
|
||||
// Delete any canonical number assignments above the new head
|
||||
for i := lastNumber + 1; ; i++ {
|
||||
hash := rawdb.ReadCanonicalHash(hc.chainDb, i)
|
||||
if hash == (common.Hash{}) {
|
||||
break
|
||||
}
|
||||
rawdb.DeleteCanonicalHash(markerBatch, i)
|
||||
}
|
||||
// Overwrite any stale canonical number assignments, going
|
||||
// backwards from the first header in this import
|
||||
var (
|
||||
headHash = headers[0].ParentHash // inserted[0].parent?
|
||||
headNumber = headers[0].Number.Uint64() - 1 // inserted[0].num-1 ?
|
||||
headHeader = hc.GetHeader(headHash, headNumber)
|
||||
)
|
||||
for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash {
|
||||
rawdb.WriteCanonicalHash(markerBatch, headHash, headNumber)
|
||||
headHash = headHeader.ParentHash
|
||||
headNumber = headHeader.Number.Uint64() - 1
|
||||
headHeader = hc.GetHeader(headHash, headNumber)
|
||||
}
|
||||
// If some of the older headers were already known, but obtained canon-status
|
||||
// during this import batch, then we need to write that now
|
||||
// Further down, we continue writing the staus for the ones that
|
||||
// were not already known
|
||||
for i := 0; i < firstInserted; i++ {
|
||||
hash := headers[i].Hash()
|
||||
num := headers[i].Number.Uint64()
|
||||
rawdb.WriteCanonicalHash(markerBatch, hash, num)
|
||||
rawdb.WriteHeadHeaderHash(markerBatch, hash)
|
||||
}
|
||||
}
|
||||
// Extend the canonical chain with the new headers
|
||||
for _, hn := range inserted {
|
||||
rawdb.WriteCanonicalHash(markerBatch, hn.hash, hn.number)
|
||||
rawdb.WriteHeadHeaderHash(markerBatch, hn.hash)
|
||||
}
|
||||
if err := markerBatch.Write(); err != nil {
|
||||
log.Crit("Failed to write header markers into disk", "err", err)
|
||||
}
|
||||
markerBatch.Reset()
|
||||
// Last step update all in-memory head header markers
|
||||
hc.currentHeaderHash = lastHash
|
||||
hc.currentHeader.Store(types.CopyHeader(lastHeader))
|
||||
headHeaderGauge.Update(lastHeader.Number.Int64())
|
||||
|
||||
// Chain status is canonical since this insert was a reorg.
|
||||
// Note that all inserts which have higher TD than existing are 'reorg'.
|
||||
status = CanonStatTy
|
||||
// Special case, all the inserted headers are already on the canonical
|
||||
// header chain, skip the reorg operation.
|
||||
if hc.GetCanonicalHash(lastHeader.Number.Uint64()) == lastHash && lastHeader.Number.Uint64() <= hc.CurrentHeader().Number.Uint64() {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if len(inserted) == 0 {
|
||||
status = NonStatTy
|
||||
// Apply the reorg operation
|
||||
if err := hc.Reorg(headers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &headerWriteResult{
|
||||
status: status,
|
||||
ignored: len(headers) - len(inserted),
|
||||
imported: len(inserted),
|
||||
lastHash: lastHash,
|
||||
lastHeader: lastHeader,
|
||||
}, nil
|
||||
result.status = CanonStatTy
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) (int, error) {
|
||||
@@ -357,7 +362,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// InsertHeaderChain inserts the given headers.
|
||||
// InsertHeaderChain inserts the given headers and does the reorganisations.
|
||||
//
|
||||
// The validity of the headers is NOT CHECKED by this method, i.e. they need to be
|
||||
// validated by ValidateHeaderChain before calling InsertHeaderChain.
|
||||
@@ -367,20 +372,19 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
|
||||
//
|
||||
// The returned 'write status' says if the inserted headers are part of the canonical chain
|
||||
// or a side chain.
|
||||
func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time) (WriteStatus, error) {
|
||||
func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time, forker *ForkChoice) (WriteStatus, error) {
|
||||
if hc.procInterrupt() {
|
||||
return 0, errors.New("aborted")
|
||||
}
|
||||
res, err := hc.writeHeaders(chain)
|
||||
|
||||
res, err := hc.writeHeadersAndSetHead(chain, forker)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Report some public statistics so the user has a clue what's going on
|
||||
context := []interface{}{
|
||||
"count", res.imported,
|
||||
"elapsed", common.PrettyDuration(time.Since(start)),
|
||||
}
|
||||
if err != nil {
|
||||
context = append(context, "err", err)
|
||||
}
|
||||
if last := res.lastHeader; last != nil {
|
||||
context = append(context, "number", last.Number, "hash", res.lastHash)
|
||||
if timestamp := time.Unix(int64(last.Time), 0); time.Since(timestamp) > time.Minute {
|
||||
@@ -495,6 +499,46 @@ func (hc *HeaderChain) GetHeaderByNumber(number uint64) *types.Header {
|
||||
return hc.GetHeader(hash, number)
|
||||
}
|
||||
|
||||
// GetHeadersFrom returns a contiguous segment of headers, in rlp-form, going
|
||||
// backwards from the given number.
|
||||
// If the 'number' is higher than the highest local header, this method will
|
||||
// return a best-effort response, containing the headers that we do have.
|
||||
func (hc *HeaderChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
|
||||
// If the request is for future headers, we still return the portion of
|
||||
// headers that we are able to serve
|
||||
if current := hc.CurrentHeader().Number.Uint64(); current < number {
|
||||
if count > number-current {
|
||||
count -= number - current
|
||||
number = current
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var headers []rlp.RawValue
|
||||
// If we have some of the headers in cache already, use that before going to db.
|
||||
hash := rawdb.ReadCanonicalHash(hc.chainDb, number)
|
||||
if hash == (common.Hash{}) {
|
||||
return nil
|
||||
}
|
||||
for count > 0 {
|
||||
header, ok := hc.headerCache.Get(hash)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
h := header.(*types.Header)
|
||||
rlpData, _ := rlp.EncodeToBytes(h)
|
||||
headers = append(headers, rlpData)
|
||||
hash = h.ParentHash
|
||||
count--
|
||||
number--
|
||||
}
|
||||
// Read remaining from db
|
||||
if count > 0 {
|
||||
headers = append(headers, rawdb.ReadHeaderRange(hc.chainDb, number, count)...)
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
func (hc *HeaderChain) GetCanonicalHash(number uint64) common.Hash {
|
||||
return rawdb.ReadCanonicalHash(hc.chainDb, number)
|
||||
}
|
||||
|
||||
+13
-12
@@ -51,10 +51,10 @@ func verifyUnbrokenCanonchain(hc *HeaderChain) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus WriteStatus, wantErr error) {
|
||||
func testInsert(t *testing.T, hc *HeaderChain, chain []*types.Header, wantStatus WriteStatus, wantErr error, forker *ForkChoice) {
|
||||
t.Helper()
|
||||
|
||||
status, err := hc.InsertHeaderChain(chain, time.Now())
|
||||
status, err := hc.InsertHeaderChain(chain, time.Now(), forker)
|
||||
if status != wantStatus {
|
||||
t.Errorf("wrong write status from InsertHeaderChain: got %v, want %v", status, wantStatus)
|
||||
}
|
||||
@@ -80,37 +80,38 @@ func TestHeaderInsertion(t *testing.T) {
|
||||
}
|
||||
// chain A: G->A1->A2...A128
|
||||
chainA := makeHeaderChain(genesis.Header(), 128, ethash.NewFaker(), db, 10)
|
||||
// chain B: G->A1->B2...B128
|
||||
// chain B: G->A1->B1...B128
|
||||
chainB := makeHeaderChain(chainA[0], 128, ethash.NewFaker(), db, 10)
|
||||
log.Root().SetHandler(log.StdoutHandler)
|
||||
|
||||
forker := NewForkChoice(hc, nil)
|
||||
// Inserting 64 headers on an empty chain, expecting
|
||||
// 1 callbacks, 1 canon-status, 0 sidestatus,
|
||||
testInsert(t, hc, chainA[:64], CanonStatTy, nil)
|
||||
testInsert(t, hc, chainA[:64], CanonStatTy, nil, forker)
|
||||
|
||||
// Inserting 64 identical headers, expecting
|
||||
// 0 callbacks, 0 canon-status, 0 sidestatus,
|
||||
testInsert(t, hc, chainA[:64], NonStatTy, nil)
|
||||
testInsert(t, hc, chainA[:64], NonStatTy, nil, forker)
|
||||
|
||||
// Inserting the same some old, some new headers
|
||||
// 1 callbacks, 1 canon, 0 side
|
||||
testInsert(t, hc, chainA[32:96], CanonStatTy, nil)
|
||||
testInsert(t, hc, chainA[32:96], CanonStatTy, nil, forker)
|
||||
|
||||
// Inserting side blocks, but not overtaking the canon chain
|
||||
testInsert(t, hc, chainB[0:32], SideStatTy, nil)
|
||||
testInsert(t, hc, chainB[0:32], SideStatTy, nil, forker)
|
||||
|
||||
// Inserting more side blocks, but we don't have the parent
|
||||
testInsert(t, hc, chainB[34:36], NonStatTy, consensus.ErrUnknownAncestor)
|
||||
testInsert(t, hc, chainB[34:36], NonStatTy, consensus.ErrUnknownAncestor, forker)
|
||||
|
||||
// Inserting more sideblocks, overtaking the canon chain
|
||||
testInsert(t, hc, chainB[32:97], CanonStatTy, nil)
|
||||
testInsert(t, hc, chainB[32:97], CanonStatTy, nil, forker)
|
||||
|
||||
// Inserting more A-headers, taking back the canonicality
|
||||
testInsert(t, hc, chainA[90:100], CanonStatTy, nil)
|
||||
testInsert(t, hc, chainA[90:100], CanonStatTy, nil, forker)
|
||||
|
||||
// And B becomes canon again
|
||||
testInsert(t, hc, chainB[97:107], CanonStatTy, nil)
|
||||
testInsert(t, hc, chainB[97:107], CanonStatTy, nil, forker)
|
||||
|
||||
// And B becomes even longer
|
||||
testInsert(t, hc, chainB[107:128], CanonStatTy, nil)
|
||||
testInsert(t, hc, chainB[107:128], CanonStatTy, nil, forker)
|
||||
}
|
||||
|
||||
@@ -242,24 +242,6 @@ func WriteLastPivotNumber(db ethdb.KeyValueWriter, pivot uint64) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFastTrieProgress retrieves the number of tries nodes fast synced to allow
|
||||
// reporting correct numbers across restarts.
|
||||
func ReadFastTrieProgress(db ethdb.KeyValueReader) uint64 {
|
||||
data, _ := db.Get(fastTrieProgressKey)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
return new(big.Int).SetBytes(data).Uint64()
|
||||
}
|
||||
|
||||
// WriteFastTrieProgress stores the fast sync trie process counter to support
|
||||
// retrieving it across restarts.
|
||||
func WriteFastTrieProgress(db ethdb.KeyValueWriter, count uint64) {
|
||||
if err := db.Put(fastTrieProgressKey, new(big.Int).SetUint64(count).Bytes()); err != nil {
|
||||
log.Crit("Failed to store fast sync trie progress", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadTxIndexTail retrieves the number of oldest indexed block
|
||||
// whose transaction indices has been indexed. If the corresponding entry
|
||||
// is non-existent in database it means the indexing has been finished.
|
||||
@@ -297,6 +279,56 @@ func WriteFastTxLookupLimit(db ethdb.KeyValueWriter, number uint64) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReadHeaderRange returns the rlp-encoded headers, starting at 'number', and going
|
||||
// backwards towards genesis. This method assumes that the caller already has
|
||||
// placed a cap on count, to prevent DoS issues.
|
||||
// Since this method operates in head-towards-genesis mode, it will return an empty
|
||||
// slice in case the head ('number') is missing. Hence, the caller must ensure that
|
||||
// the head ('number') argument is actually an existing header.
|
||||
//
|
||||
// N.B: Since the input is a number, as opposed to a hash, it's implicit that
|
||||
// this method only operates on canon headers.
|
||||
func ReadHeaderRange(db ethdb.Reader, number uint64, count uint64) []rlp.RawValue {
|
||||
var rlpHeaders []rlp.RawValue
|
||||
if count == 0 {
|
||||
return rlpHeaders
|
||||
}
|
||||
i := number
|
||||
if count-1 > number {
|
||||
// It's ok to request block 0, 1 item
|
||||
count = number + 1
|
||||
}
|
||||
limit, _ := db.Ancients()
|
||||
// First read live blocks
|
||||
if i >= limit {
|
||||
// If we need to read live blocks, we need to figure out the hash first
|
||||
hash := ReadCanonicalHash(db, number)
|
||||
for ; i >= limit && count > 0; i-- {
|
||||
if data, _ := db.Get(headerKey(i, hash)); len(data) > 0 {
|
||||
rlpHeaders = append(rlpHeaders, data)
|
||||
// Get the parent hash for next query
|
||||
hash = types.HeaderParentHashFromRLP(data)
|
||||
} else {
|
||||
break // Maybe got moved to ancients
|
||||
}
|
||||
count--
|
||||
}
|
||||
}
|
||||
if count == 0 {
|
||||
return rlpHeaders
|
||||
}
|
||||
// read remaining from ancients
|
||||
max := count * 700
|
||||
data, err := db.AncientRange(freezerHeaderTable, i+1-count, count, max)
|
||||
if err == nil && uint64(len(data)) == count {
|
||||
// the data is on the order [h, h+1, .., n] -- reordering needed
|
||||
for i := range data {
|
||||
rlpHeaders = append(rlpHeaders, data[len(data)-1-i])
|
||||
}
|
||||
}
|
||||
return rlpHeaders
|
||||
}
|
||||
|
||||
// ReadHeaderRLP retrieves a block header in its raw RLP database encoding.
|
||||
func ReadHeaderRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue {
|
||||
var data []byte
|
||||
@@ -682,7 +714,7 @@ func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.C
|
||||
if logs := readLegacyLogs(db, hash, number, config); logs != nil {
|
||||
return logs
|
||||
}
|
||||
log.Error("Invalid receipt array RLP", "hash", "err", err)
|
||||
log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -883,3 +883,67 @@ func BenchmarkDecodeRLPLogs(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHeadersRLPStorage(t *testing.T) {
|
||||
// Have N headers in the freezer
|
||||
frdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp freezer dir: %v", err)
|
||||
}
|
||||
defer os.Remove(frdir)
|
||||
|
||||
db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create database with ancient backend")
|
||||
}
|
||||
defer db.Close()
|
||||
// Create blocks
|
||||
var chain []*types.Block
|
||||
var pHash common.Hash
|
||||
for i := 0; i < 100; i++ {
|
||||
block := types.NewBlockWithHeader(&types.Header{
|
||||
Number: big.NewInt(int64(i)),
|
||||
Extra: []byte("test block"),
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
TxHash: types.EmptyRootHash,
|
||||
ReceiptHash: types.EmptyRootHash,
|
||||
ParentHash: pHash,
|
||||
})
|
||||
chain = append(chain, block)
|
||||
pHash = block.Hash()
|
||||
}
|
||||
var receipts []types.Receipts = make([]types.Receipts, 100)
|
||||
// Write first half to ancients
|
||||
WriteAncientBlocks(db, chain[:50], receipts[:50], big.NewInt(100))
|
||||
// Write second half to db
|
||||
for i := 50; i < 100; i++ {
|
||||
WriteCanonicalHash(db, chain[i].Hash(), chain[i].NumberU64())
|
||||
WriteBlock(db, chain[i])
|
||||
}
|
||||
checkSequence := func(from, amount int) {
|
||||
headersRlp := ReadHeaderRange(db, uint64(from), uint64(amount))
|
||||
if have, want := len(headersRlp), amount; have != want {
|
||||
t.Fatalf("have %d headers, want %d", have, want)
|
||||
}
|
||||
for i, headerRlp := range headersRlp {
|
||||
var header types.Header
|
||||
if err := rlp.DecodeBytes(headerRlp, &header); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if have, want := header.Number.Uint64(), uint64(from-i); have != want {
|
||||
t.Fatalf("wrong number, have %d want %d", have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
checkSequence(99, 20) // Latest block and 19 parents
|
||||
checkSequence(99, 50) // Latest block -> all db blocks
|
||||
checkSequence(99, 51) // Latest block -> one from ancients
|
||||
checkSequence(99, 52) // Latest blocks -> two from ancients
|
||||
checkSequence(50, 2) // One from db, one from ancients
|
||||
checkSequence(49, 1) // One from ancients
|
||||
checkSequence(49, 50) // All ancient ones
|
||||
checkSequence(99, 100) // All blocks
|
||||
checkSequence(0, 1) // Only genesis
|
||||
checkSequence(1, 1) // Only block 1
|
||||
checkSequence(1, 2) // Genesis + block 1
|
||||
}
|
||||
|
||||
@@ -138,3 +138,38 @@ func PopUncleanShutdownMarker(db ethdb.KeyValueStore) {
|
||||
log.Warn("Failed to clear unclean-shutdown marker", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUncleanShutdownMarker updates the last marker's timestamp to now.
|
||||
func UpdateUncleanShutdownMarker(db ethdb.KeyValueStore) {
|
||||
var uncleanShutdowns crashList
|
||||
// Read old data
|
||||
if data, err := db.Get(uncleanShutdownKey); err != nil {
|
||||
log.Warn("Error reading unclean shutdown markers", "error", err)
|
||||
} else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil {
|
||||
log.Warn("Error decoding unclean shutdown markers", "error", err)
|
||||
}
|
||||
// This shouldn't happen because we push a marker on Backend instantiation
|
||||
count := len(uncleanShutdowns.Recent)
|
||||
if count == 0 {
|
||||
log.Warn("No unclean shutdown marker to update")
|
||||
return
|
||||
}
|
||||
uncleanShutdowns.Recent[count-1] = uint64(time.Now().Unix())
|
||||
data, _ := rlp.EncodeToBytes(uncleanShutdowns)
|
||||
if err := db.Put(uncleanShutdownKey, data); err != nil {
|
||||
log.Warn("Failed to write unclean-shutdown marker", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadTransitionStatus retrieves the eth2 transition status from the database
|
||||
func ReadTransitionStatus(db ethdb.KeyValueReader) []byte {
|
||||
data, _ := db.Get(transitionStatusKey)
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteTransitionStatus stores the eth2 transition status to the database
|
||||
func WriteTransitionStatus(db ethdb.KeyValueWriter, data []byte) {
|
||||
if err := db.Put(transitionStatusKey, data); err != nil {
|
||||
log.Crit("Failed to store the eth2 transition status", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,11 +208,3 @@ func WriteSnapshotSyncStatus(db ethdb.KeyValueWriter, status []byte) {
|
||||
log.Crit("Failed to store snapshot sync status", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSnapshotSyncStatus deletes the serialized sync status saved at the last
|
||||
// shutdown
|
||||
func DeleteSnapshotSyncStatus(db ethdb.KeyValueWriter) {
|
||||
if err := db.Delete(snapshotSyncStatusKey); err != nil {
|
||||
log.Crit("Failed to remove snapshot sync status", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,8 @@ func indexTransactions(db ethdb.Database, from uint64, to uint64, interrupt chan
|
||||
}
|
||||
}
|
||||
|
||||
// IndexTransactions creates txlookup indices of the specified block range.
|
||||
// IndexTransactions creates txlookup indices of the specified block range. The from
|
||||
// is included while to is excluded.
|
||||
//
|
||||
// This function iterates canonical chain in reverse order, it has one main advantage:
|
||||
// We can write tx index tail flag periodically even without the whole indexing
|
||||
@@ -339,6 +340,7 @@ func unindexTransactions(db ethdb.Database, from uint64, to uint64, interrupt ch
|
||||
}
|
||||
|
||||
// UnindexTransactions removes txlookup indices of the specified block range.
|
||||
// The from is included while to is excluded.
|
||||
//
|
||||
// There is a passed channel, the whole procedure will be interrupted if any
|
||||
// signal received.
|
||||
|
||||
@@ -395,7 +395,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
|
||||
databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey,
|
||||
fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey,
|
||||
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
|
||||
uncleanShutdownKey, badBlockKey,
|
||||
uncleanShutdownKey, badBlockKey, transitionStatusKey,
|
||||
} {
|
||||
if bytes.Equal(key, meta) {
|
||||
metadata.Add(size)
|
||||
|
||||
@@ -75,6 +75,9 @@ var (
|
||||
// uncleanShutdownKey tracks the list of local crashes
|
||||
uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db
|
||||
|
||||
// transitionStatusKey tracks the eth2 transition status.
|
||||
transitionStatusKey = []byte("eth2-transition")
|
||||
|
||||
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes).
|
||||
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
|
||||
headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td
|
||||
|
||||
@@ -48,13 +48,13 @@ var (
|
||||
// accountCheckRange is the upper limit of the number of accounts involved in
|
||||
// each range check. This is a value estimated based on experience. If this
|
||||
// value is too large, the failure rate of range prove will increase. Otherwise
|
||||
// the the value is too small, the efficiency of the state recovery will decrease.
|
||||
// the value is too small, the efficiency of the state recovery will decrease.
|
||||
accountCheckRange = 128
|
||||
|
||||
// storageCheckRange is the upper limit of the number of storage slots involved
|
||||
// in each range check. This is a value estimated based on experience. If this
|
||||
// value is too large, the failure rate of range prove will increase. Otherwise
|
||||
// the the value is too small, the efficiency of the state recovery will decrease.
|
||||
// the value is too small, the efficiency of the state recovery will decrease.
|
||||
storageCheckRange = 1024
|
||||
|
||||
// errMissingTrie is returned if the target trie is missing while the generation
|
||||
|
||||
@@ -21,67 +21,11 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
// wipeSnapshot starts a goroutine to iterate over the entire key-value database
|
||||
// and delete all the data associated with the snapshot (accounts, storage,
|
||||
// metadata). After all is done, the snapshot range of the database is compacted
|
||||
// to free up unused data blocks.
|
||||
func wipeSnapshot(db ethdb.KeyValueStore, full bool) chan struct{} {
|
||||
// Wipe the snapshot root marker synchronously
|
||||
if full {
|
||||
rawdb.DeleteSnapshotRoot(db)
|
||||
}
|
||||
// Wipe everything else asynchronously
|
||||
wiper := make(chan struct{}, 1)
|
||||
go func() {
|
||||
if err := wipeContent(db); err != nil {
|
||||
log.Error("Failed to wipe state snapshot", "err", err) // Database close will trigger this
|
||||
return
|
||||
}
|
||||
close(wiper)
|
||||
}()
|
||||
return wiper
|
||||
}
|
||||
|
||||
// wipeContent iterates over the entire key-value database and deletes all the
|
||||
// data associated with the snapshot (accounts, storage), but not the root hash
|
||||
// as the wiper is meant to run on a background thread but the root needs to be
|
||||
// removed in sync to avoid data races. After all is done, the snapshot range of
|
||||
// the database is compacted to free up unused data blocks.
|
||||
func wipeContent(db ethdb.KeyValueStore) error {
|
||||
if err := wipeKeyRange(db, "accounts", rawdb.SnapshotAccountPrefix, nil, nil, len(rawdb.SnapshotAccountPrefix)+common.HashLength, snapWipedAccountMeter, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := wipeKeyRange(db, "storage", rawdb.SnapshotStoragePrefix, nil, nil, len(rawdb.SnapshotStoragePrefix)+2*common.HashLength, snapWipedStorageMeter, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// Compact the snapshot section of the database to get rid of unused space
|
||||
start := time.Now()
|
||||
|
||||
log.Info("Compacting snapshot account area ")
|
||||
end := common.CopyBytes(rawdb.SnapshotAccountPrefix)
|
||||
end[len(end)-1]++
|
||||
|
||||
if err := db.Compact(rawdb.SnapshotAccountPrefix, end); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Compacting snapshot storage area ")
|
||||
end = common.CopyBytes(rawdb.SnapshotStoragePrefix)
|
||||
end[len(end)-1]++
|
||||
|
||||
if err := db.Compact(rawdb.SnapshotStoragePrefix, end); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Compacted snapshot area in database", "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// wipeKeyRange deletes a range of keys from the database starting with prefix
|
||||
// and having a specific total key length. The start and limit is optional for
|
||||
// specifying a particular key range for deletion.
|
||||
|
||||
@@ -30,95 +30,50 @@ import (
|
||||
func TestWipe(t *testing.T) {
|
||||
// Create a database with some random snapshot data
|
||||
db := memorydb.New()
|
||||
|
||||
for i := 0; i < 128; i++ {
|
||||
account := randomHash()
|
||||
rawdb.WriteAccountSnapshot(db, account, randomHash().Bytes())
|
||||
for j := 0; j < 1024; j++ {
|
||||
rawdb.WriteStorageSnapshot(db, account, randomHash(), randomHash().Bytes())
|
||||
}
|
||||
rawdb.WriteAccountSnapshot(db, randomHash(), randomHash().Bytes())
|
||||
}
|
||||
rawdb.WriteSnapshotRoot(db, randomHash())
|
||||
|
||||
// Add some random non-snapshot data too to make wiping harder
|
||||
for i := 0; i < 65536; i++ {
|
||||
// Generate a key that's the wrong length for a state snapshot item
|
||||
var keysize int
|
||||
for keysize == 0 || keysize == 32 || keysize == 64 {
|
||||
keysize = 8 + rand.Intn(64) // +8 to ensure we will "never" randomize duplicates
|
||||
}
|
||||
// Randomize the suffix, dedup and inject it under the snapshot namespace
|
||||
keysuffix := make([]byte, keysize)
|
||||
for i := 0; i < 500; i++ {
|
||||
// Generate keys with wrong length for a state snapshot item
|
||||
keysuffix := make([]byte, 31)
|
||||
rand.Read(keysuffix)
|
||||
|
||||
if rand.Int31n(2) == 0 {
|
||||
db.Put(append(rawdb.SnapshotAccountPrefix, keysuffix...), randomHash().Bytes())
|
||||
} else {
|
||||
db.Put(append(rawdb.SnapshotStoragePrefix, keysuffix...), randomHash().Bytes())
|
||||
db.Put(append(rawdb.SnapshotAccountPrefix, keysuffix...), randomHash().Bytes())
|
||||
keysuffix = make([]byte, 33)
|
||||
rand.Read(keysuffix)
|
||||
db.Put(append(rawdb.SnapshotAccountPrefix, keysuffix...), randomHash().Bytes())
|
||||
}
|
||||
count := func() (items int) {
|
||||
it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
|
||||
defer it.Release()
|
||||
for it.Next() {
|
||||
if len(it.Key()) == len(rawdb.SnapshotAccountPrefix)+common.HashLength {
|
||||
items++
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
// Sanity check that all the keys are present
|
||||
var items int
|
||||
|
||||
it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
if len(key) == len(rawdb.SnapshotAccountPrefix)+common.HashLength {
|
||||
items++
|
||||
}
|
||||
if items := count(); items != 128 {
|
||||
t.Fatalf("snapshot size mismatch: have %d, want %d", items, 128)
|
||||
}
|
||||
it = db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
if len(key) == len(rawdb.SnapshotStoragePrefix)+2*common.HashLength {
|
||||
items++
|
||||
}
|
||||
// Wipe the accounts
|
||||
if err := wipeKeyRange(db, "accounts", rawdb.SnapshotAccountPrefix, nil, nil,
|
||||
len(rawdb.SnapshotAccountPrefix)+common.HashLength, snapWipedAccountMeter, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if items != 128+128*1024 {
|
||||
t.Fatalf("snapshot size mismatch: have %d, want %d", items, 128+128*1024)
|
||||
}
|
||||
if hash := rawdb.ReadSnapshotRoot(db); hash == (common.Hash{}) {
|
||||
t.Errorf("snapshot block marker mismatch: have %#x, want <not-nil>", hash)
|
||||
}
|
||||
// Wipe all snapshot entries from the database
|
||||
<-wipeSnapshot(db, true)
|
||||
|
||||
// Iterate over the database end ensure no snapshot information remains
|
||||
it = db.NewIterator(rawdb.SnapshotAccountPrefix, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
if len(key) == len(rawdb.SnapshotAccountPrefix)+common.HashLength {
|
||||
t.Errorf("snapshot entry remained after wipe: %x", key)
|
||||
}
|
||||
}
|
||||
it = db.NewIterator(rawdb.SnapshotStoragePrefix, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
if len(key) == len(rawdb.SnapshotStoragePrefix)+2*common.HashLength {
|
||||
t.Errorf("snapshot entry remained after wipe: %x", key)
|
||||
}
|
||||
}
|
||||
if hash := rawdb.ReadSnapshotRoot(db); hash != (common.Hash{}) {
|
||||
t.Errorf("snapshot block marker remained after wipe: %#x", hash)
|
||||
if items := count(); items != 0 {
|
||||
t.Fatalf("snapshot size mismatch: have %d, want %d", items, 0)
|
||||
}
|
||||
// Iterate over the database and ensure miscellaneous items are present
|
||||
items = 0
|
||||
|
||||
it = db.NewIterator(nil, nil)
|
||||
items := 0
|
||||
it := db.NewIterator(nil, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
items++
|
||||
}
|
||||
if items != 65536 {
|
||||
t.Fatalf("misc item count mismatch: have %d, want %d", items, 65536)
|
||||
if items != 1000 {
|
||||
t.Fatalf("misc item count mismatch: have %d, want %d", items, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
// NewStateSync create a new state trie download scheduler.
|
||||
func NewStateSync(root common.Hash, database ethdb.KeyValueReader, bloom *trie.SyncBloom, onLeaf func(paths [][]byte, leaf []byte) error) *trie.Sync {
|
||||
func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(paths [][]byte, leaf []byte) error) *trie.Sync {
|
||||
// Register the storage slot callback if the external callback is specified.
|
||||
var onSlot func(paths [][]byte, hexpath []byte, leaf []byte, parent common.Hash) error
|
||||
if onLeaf != nil {
|
||||
@@ -52,6 +52,6 @@ func NewStateSync(root common.Hash, database ethdb.KeyValueReader, bloom *trie.S
|
||||
syncer.AddCodeEntry(common.BytesToHash(obj.CodeHash), hexpath, parent)
|
||||
return nil
|
||||
}
|
||||
syncer = trie.NewSync(root, database, onAccount, bloom)
|
||||
syncer = trie.NewSync(root, database, onAccount)
|
||||
return syncer
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/ethdb/memorydb"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
)
|
||||
@@ -134,7 +133,7 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error {
|
||||
// Tests that an empty state is not scheduled for syncing.
|
||||
func TestEmptyStateSync(t *testing.T) {
|
||||
empty := common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), trie.NewSyncBloom(1, memorydb.New()), nil)
|
||||
sync := NewStateSync(empty, rawdb.NewMemoryDatabase(), nil)
|
||||
if nodes, paths, codes := sync.Missing(1); len(nodes) != 0 || len(paths) != 0 || len(codes) != 0 {
|
||||
t.Errorf(" content requested for empty state: %v, %v, %v", nodes, paths, codes)
|
||||
}
|
||||
@@ -171,7 +170,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) {
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)
|
||||
sched := NewStateSync(srcRoot, dstDb, nil)
|
||||
|
||||
nodes, paths, codes := sched.Missing(count)
|
||||
var (
|
||||
@@ -250,7 +249,7 @@ func TestIterativeDelayedStateSync(t *testing.T) {
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)
|
||||
sched := NewStateSync(srcRoot, dstDb, nil)
|
||||
|
||||
nodes, _, codes := sched.Missing(0)
|
||||
queue := append(append([]common.Hash{}, nodes...), codes...)
|
||||
@@ -298,7 +297,7 @@ func testIterativeRandomStateSync(t *testing.T, count int) {
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)
|
||||
sched := NewStateSync(srcRoot, dstDb, nil)
|
||||
|
||||
queue := make(map[common.Hash]struct{})
|
||||
nodes, _, codes := sched.Missing(count)
|
||||
@@ -348,7 +347,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)
|
||||
sched := NewStateSync(srcRoot, dstDb, nil)
|
||||
|
||||
queue := make(map[common.Hash]struct{})
|
||||
nodes, _, codes := sched.Missing(0)
|
||||
@@ -415,7 +414,7 @@ func TestIncompleteStateSync(t *testing.T) {
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb := rawdb.NewMemoryDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb, trie.NewSyncBloom(1, dstDb), nil)
|
||||
sched := NewStateSync(srcRoot, dstDb, nil)
|
||||
|
||||
var added []common.Hash
|
||||
|
||||
|
||||
+2
-3
@@ -621,9 +621,8 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||
if err != nil {
|
||||
return ErrInvalidSender
|
||||
}
|
||||
// Drop non-local transactions under our own minimal accepted gas price or tip.
|
||||
pendingBaseFee := pool.priced.urgent.baseFee
|
||||
if !local && tx.EffectiveGasTipIntCmp(pool.gasPrice, pendingBaseFee) < 0 {
|
||||
// Drop non-local transactions under our own minimal accepted gas price or tip
|
||||
if !local && tx.GasTipCapIntCmp(pool.gasPrice) < 0 {
|
||||
return ErrUnderpriced
|
||||
}
|
||||
// Ensure the transaction adheres to nonce ordering
|
||||
|
||||
@@ -85,6 +85,12 @@ type Header struct {
|
||||
|
||||
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
|
||||
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
|
||||
|
||||
/*
|
||||
TODO (MariusVanDerWijden) Add this field once needed
|
||||
// Random was added during the merge and contains the BeaconState randomness
|
||||
Random common.Hash `json:"random" rlp:"optional"`
|
||||
*/
|
||||
}
|
||||
|
||||
// field type overrides for gencodec
|
||||
@@ -383,3 +389,21 @@ func (b *Block) Hash() common.Hash {
|
||||
}
|
||||
|
||||
type Blocks []*Block
|
||||
|
||||
// HeaderParentHashFromRLP returns the parentHash of an RLP-encoded
|
||||
// header. If 'header' is invalid, the zero hash is returned.
|
||||
func HeaderParentHashFromRLP(header []byte) common.Hash {
|
||||
// parentHash is the first list element.
|
||||
listContent, _, err := rlp.SplitList(header)
|
||||
if err != nil {
|
||||
return common.Hash{}
|
||||
}
|
||||
parentHash, _, err := rlp.SplitString(listContent)
|
||||
if err != nil {
|
||||
return common.Hash{}
|
||||
}
|
||||
if len(parentHash) != 32 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return common.BytesToHash(parentHash)
|
||||
}
|
||||
|
||||
@@ -281,3 +281,64 @@ func makeBenchBlock() *Block {
|
||||
}
|
||||
return NewBlock(header, txs, uncles, receipts, newHasher())
|
||||
}
|
||||
|
||||
func TestRlpDecodeParentHash(t *testing.T) {
|
||||
// A minimum one
|
||||
want := common.HexToHash("0x112233445566778899001122334455667788990011223344556677889900aabb")
|
||||
if rlpData, err := rlp.EncodeToBytes(Header{ParentHash: want}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if have := HeaderParentHashFromRLP(rlpData); have != want {
|
||||
t.Fatalf("have %x, want %x", have, want)
|
||||
}
|
||||
}
|
||||
// And a maximum one
|
||||
// | Difficulty | dynamic| *big.Int | 0x5ad3c2c71bbff854908 (current mainnet TD: 76 bits) |
|
||||
// | Number | dynamic| *big.Int | 64 bits |
|
||||
// | Extra | dynamic| []byte | 65+32 byte (clique) |
|
||||
// | BaseFee | dynamic| *big.Int | 64 bits |
|
||||
mainnetTd := new(big.Int)
|
||||
mainnetTd.SetString("5ad3c2c71bbff854908", 16)
|
||||
if rlpData, err := rlp.EncodeToBytes(Header{
|
||||
ParentHash: want,
|
||||
Difficulty: mainnetTd,
|
||||
Number: new(big.Int).SetUint64(math.MaxUint64),
|
||||
Extra: make([]byte, 65+32),
|
||||
BaseFee: new(big.Int).SetUint64(math.MaxUint64),
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if have := HeaderParentHashFromRLP(rlpData); have != want {
|
||||
t.Fatalf("have %x, want %x", have, want)
|
||||
}
|
||||
}
|
||||
// Also test a very very large header.
|
||||
{
|
||||
// The rlp-encoding of the heder belowCauses _total_ length of 65540,
|
||||
// which is the first to blow the fast-path.
|
||||
h := Header{
|
||||
ParentHash: want,
|
||||
Extra: make([]byte, 65041),
|
||||
}
|
||||
if rlpData, err := rlp.EncodeToBytes(h); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if have := HeaderParentHashFromRLP(rlpData); have != want {
|
||||
t.Fatalf("have %x, want %x", have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// Test some invalid erroneous stuff
|
||||
for i, rlpData := range [][]byte{
|
||||
nil,
|
||||
common.FromHex("0x"),
|
||||
common.FromHex("0x01"),
|
||||
common.FromHex("0x3031323334"),
|
||||
} {
|
||||
if have, want := HeaderParentHashFromRLP(rlpData), (common.Hash{}); have != want {
|
||||
t.Fatalf("invalid %d: have %x, want %x", i, have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ import (
|
||||
type DynamicFeeTx struct {
|
||||
ChainID *big.Int
|
||||
Nonce uint64
|
||||
GasTipCap *big.Int
|
||||
GasFeeCap *big.Int
|
||||
GasTipCap *big.Int // a.k.a. maxPriorityFeePerGas
|
||||
GasFeeCap *big.Int // a.k.a. maxFeePerGas
|
||||
Gas uint64
|
||||
To *common.Address `rlp:"nil"` // nil means contract creation
|
||||
Value *big.Int
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
// Copyright 2021 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 vm
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// accessList is an accumulator for the set of accounts and storage slots an EVM
|
||||
// contract execution touches.
|
||||
type accessList map[common.Address]accessListSlots
|
||||
|
||||
// accessListSlots is an accumulator for the set of storage slots within a single
|
||||
// contract that an EVM contract execution touches.
|
||||
type accessListSlots map[common.Hash]struct{}
|
||||
|
||||
// newAccessList creates a new accessList.
|
||||
func newAccessList() accessList {
|
||||
return make(map[common.Address]accessListSlots)
|
||||
}
|
||||
|
||||
// addAddress adds an address to the accesslist.
|
||||
func (al accessList) addAddress(address common.Address) {
|
||||
// Set address if not previously present
|
||||
if _, present := al[address]; !present {
|
||||
al[address] = make(map[common.Hash]struct{})
|
||||
}
|
||||
}
|
||||
|
||||
// addSlot adds a storage slot to the accesslist.
|
||||
func (al accessList) addSlot(address common.Address, slot common.Hash) {
|
||||
// Set address if not previously present
|
||||
al.addAddress(address)
|
||||
|
||||
// Set the slot on the surely existent storage set
|
||||
al[address][slot] = struct{}{}
|
||||
}
|
||||
|
||||
// equal checks if the content of the current access list is the same as the
|
||||
// content of the other one.
|
||||
func (al accessList) equal(other accessList) bool {
|
||||
// Cross reference the accounts first
|
||||
if len(al) != len(other) {
|
||||
return false
|
||||
}
|
||||
for addr := range al {
|
||||
if _, ok := other[addr]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for addr := range other {
|
||||
if _, ok := al[addr]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Accounts match, cross reference the storage slots too
|
||||
for addr, slots := range al {
|
||||
otherslots := other[addr]
|
||||
|
||||
if len(slots) != len(otherslots) {
|
||||
return false
|
||||
}
|
||||
for hash := range slots {
|
||||
if _, ok := otherslots[hash]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for hash := range otherslots {
|
||||
if _, ok := slots[hash]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// accesslist converts the accesslist to a types.AccessList.
|
||||
func (al accessList) accessList() types.AccessList {
|
||||
acl := make(types.AccessList, 0, len(al))
|
||||
for addr, slots := range al {
|
||||
tuple := types.AccessTuple{Address: addr, StorageKeys: []common.Hash{}}
|
||||
for slot := range slots {
|
||||
tuple.StorageKeys = append(tuple.StorageKeys, slot)
|
||||
}
|
||||
acl = append(acl, tuple)
|
||||
}
|
||||
return acl
|
||||
}
|
||||
|
||||
// AccessListTracer is a tracer that accumulates touched accounts and storage
|
||||
// slots into an internal set.
|
||||
type AccessListTracer struct {
|
||||
excl map[common.Address]struct{} // Set of account to exclude from the list
|
||||
list accessList // Set of accounts and storage slots touched
|
||||
}
|
||||
|
||||
// NewAccessListTracer creates a new tracer that can generate AccessLists.
|
||||
// An optional AccessList can be specified to occupy slots and addresses in
|
||||
// the resulting accesslist.
|
||||
func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer {
|
||||
excl := map[common.Address]struct{}{
|
||||
from: {}, to: {},
|
||||
}
|
||||
for _, addr := range precompiles {
|
||||
excl[addr] = struct{}{}
|
||||
}
|
||||
list := newAccessList()
|
||||
for _, al := range acl {
|
||||
if _, ok := excl[al.Address]; !ok {
|
||||
list.addAddress(al.Address)
|
||||
}
|
||||
for _, slot := range al.StorageKeys {
|
||||
list.addSlot(al.Address, slot)
|
||||
}
|
||||
}
|
||||
return &AccessListTracer{
|
||||
excl: excl,
|
||||
list: list,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AccessListTracer) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist.
|
||||
func (a *AccessListTracer) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
|
||||
stack := scope.Stack
|
||||
if (op == SLOAD || op == SSTORE) && stack.len() >= 1 {
|
||||
slot := common.Hash(stack.data[stack.len()-1].Bytes32())
|
||||
a.list.addSlot(scope.Contract.Address(), slot)
|
||||
}
|
||||
if (op == EXTCODECOPY || op == EXTCODEHASH || op == EXTCODESIZE || op == BALANCE || op == SELFDESTRUCT) && stack.len() >= 1 {
|
||||
addr := common.Address(stack.data[stack.len()-1].Bytes20())
|
||||
if _, ok := a.excl[addr]; !ok {
|
||||
a.list.addAddress(addr)
|
||||
}
|
||||
}
|
||||
if (op == DELEGATECALL || op == CALL || op == STATICCALL || op == CALLCODE) && stack.len() >= 5 {
|
||||
addr := common.Address(stack.data[stack.len()-2].Bytes20())
|
||||
if _, ok := a.excl[addr]; !ok {
|
||||
a.list.addAddress(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (*AccessListTracer) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {}
|
||||
|
||||
func (*AccessListTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
||||
// AccessList returns the current accesslist maintained by the tracer.
|
||||
func (a *AccessListTracer) AccessList() types.AccessList {
|
||||
return a.list.accessList()
|
||||
}
|
||||
|
||||
// Equal returns if the content of two access list traces are equal.
|
||||
func (a *AccessListTracer) Equal(other *AccessListTracer) bool {
|
||||
return a.list.equal(other.list)
|
||||
}
|
||||
+13
-19
@@ -17,12 +17,12 @@
|
||||
package vm
|
||||
|
||||
const (
|
||||
set2BitsMask = uint16(0b1100_0000_0000_0000)
|
||||
set3BitsMask = uint16(0b1110_0000_0000_0000)
|
||||
set4BitsMask = uint16(0b1111_0000_0000_0000)
|
||||
set5BitsMask = uint16(0b1111_1000_0000_0000)
|
||||
set6BitsMask = uint16(0b1111_1100_0000_0000)
|
||||
set7BitsMask = uint16(0b1111_1110_0000_0000)
|
||||
set2BitsMask = uint16(0b11)
|
||||
set3BitsMask = uint16(0b111)
|
||||
set4BitsMask = uint16(0b1111)
|
||||
set5BitsMask = uint16(0b1_1111)
|
||||
set6BitsMask = uint16(0b11_1111)
|
||||
set7BitsMask = uint16(0b111_1111)
|
||||
)
|
||||
|
||||
// bitvec is a bit vector which maps bytes in a program.
|
||||
@@ -30,32 +30,26 @@ const (
|
||||
// it's data (i.e. argument of PUSHxx).
|
||||
type bitvec []byte
|
||||
|
||||
var lookup = [8]byte{
|
||||
0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1,
|
||||
}
|
||||
|
||||
func (bits bitvec) set1(pos uint64) {
|
||||
bits[pos/8] |= lookup[pos%8]
|
||||
bits[pos/8] |= 1 << (pos % 8)
|
||||
}
|
||||
|
||||
func (bits bitvec) setN(flag uint16, pos uint64) {
|
||||
a := flag >> (pos % 8)
|
||||
bits[pos/8] |= byte(a >> 8)
|
||||
if b := byte(a); b != 0 {
|
||||
// If the bit-setting affects the neighbouring byte, we can assign - no need to OR it,
|
||||
// since it's the first write to that byte
|
||||
a := flag << (pos % 8)
|
||||
bits[pos/8] |= byte(a)
|
||||
if b := byte(a >> 8); b != 0 {
|
||||
bits[pos/8+1] = b
|
||||
}
|
||||
}
|
||||
|
||||
func (bits bitvec) set8(pos uint64) {
|
||||
a := byte(0xFF >> (pos % 8))
|
||||
a := byte(0xFF << (pos % 8))
|
||||
bits[pos/8] |= a
|
||||
bits[pos/8+1] = ^a
|
||||
}
|
||||
|
||||
func (bits bitvec) set16(pos uint64) {
|
||||
a := byte(0xFF >> (pos % 8))
|
||||
a := byte(0xFF << (pos % 8))
|
||||
bits[pos/8] |= a
|
||||
bits[pos/8+1] = 0xFF
|
||||
bits[pos/8+2] = ^a
|
||||
@@ -63,7 +57,7 @@ func (bits bitvec) set16(pos uint64) {
|
||||
|
||||
// codeSegment checks if the position is in a code segment.
|
||||
func (bits *bitvec) codeSegment(pos uint64) bool {
|
||||
return ((*bits)[pos/8] & (0x80 >> (pos % 8))) == 0
|
||||
return (((*bits)[pos/8] >> (pos % 8)) & 1) == 0
|
||||
}
|
||||
|
||||
// codeBitmap collects data locations in code.
|
||||
|
||||
+22
-18
@@ -17,6 +17,7 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
@@ -28,24 +29,27 @@ func TestJumpDestAnalysis(t *testing.T) {
|
||||
exp byte
|
||||
which int
|
||||
}{
|
||||
{[]byte{byte(PUSH1), 0x01, 0x01, 0x01}, 0x40, 0},
|
||||
{[]byte{byte(PUSH1), byte(PUSH1), byte(PUSH1), byte(PUSH1)}, 0x50, 0},
|
||||
{[]byte{byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), 0x01, 0x01, 0x01}, 0x7F, 0},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x80, 1},
|
||||
{[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), byte(PUSH2), byte(PUSH2), 0x01, 0x01, 0x01}, 0x03, 0},
|
||||
{[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), 0x01, 0x01, 0x01, 0x01, 0x01}, 0x00, 1},
|
||||
{[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x74, 0},
|
||||
{[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x00, 1},
|
||||
{[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x3F, 0},
|
||||
{[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0xC0, 1},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x7F, 0},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0xFF, 1},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0x80, 2},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0x7f, 0},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0xA0, 1},
|
||||
{[]byte{byte(PUSH32)}, 0x7F, 0},
|
||||
{[]byte{byte(PUSH32)}, 0xFF, 1},
|
||||
{[]byte{byte(PUSH32)}, 0xFF, 2},
|
||||
{[]byte{byte(PUSH1), 0x01, 0x01, 0x01}, 0b0000_0010, 0},
|
||||
{[]byte{byte(PUSH1), byte(PUSH1), byte(PUSH1), byte(PUSH1)}, 0b0000_1010, 0},
|
||||
{[]byte{0x00, byte(PUSH1), 0x00, byte(PUSH1), 0x00, byte(PUSH1), 0x00, byte(PUSH1)}, 0b0101_0100, 0},
|
||||
{[]byte{byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), byte(PUSH8), 0x01, 0x01, 0x01}, bits.Reverse8(0x7F), 0},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0001, 1},
|
||||
{[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), byte(PUSH2), byte(PUSH2), 0x01, 0x01, 0x01}, 0b1100_0000, 0},
|
||||
{[]byte{0x01, 0x01, 0x01, 0x01, 0x01, byte(PUSH2), 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0000, 1},
|
||||
{[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0010_1110, 0},
|
||||
{[]byte{byte(PUSH3), 0x01, 0x01, 0x01, byte(PUSH1), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0000, 1},
|
||||
{[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1100, 0},
|
||||
{[]byte{0x01, byte(PUSH8), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0011, 1},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1110, 0},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b1111_1111, 1},
|
||||
{[]byte{byte(PUSH16), 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}, 0b0000_0001, 2},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0b1111_1110, 0},
|
||||
{[]byte{byte(PUSH8), 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, byte(PUSH1), 0x01}, 0b0000_0101, 1},
|
||||
{[]byte{byte(PUSH32)}, 0b1111_1110, 0},
|
||||
{[]byte{byte(PUSH32)}, 0b1111_1111, 1},
|
||||
{[]byte{byte(PUSH32)}, 0b1111_1111, 2},
|
||||
{[]byte{byte(PUSH32)}, 0b1111_1111, 3},
|
||||
{[]byte{byte(PUSH32)}, 0b0000_0001, 4},
|
||||
}
|
||||
for i, test := range tests {
|
||||
ret := codeBitmap(test.code)
|
||||
|
||||
+2
-7
@@ -143,16 +143,11 @@ func (c *Contract) AsDelegate() *Contract {
|
||||
|
||||
// GetOp returns the n'th element in the contract's byte array
|
||||
func (c *Contract) GetOp(n uint64) OpCode {
|
||||
return OpCode(c.GetByte(n))
|
||||
}
|
||||
|
||||
// GetByte returns the n'th byte in the contract's byte array
|
||||
func (c *Contract) GetByte(n uint64) byte {
|
||||
if n < uint64(len(c.Code)) {
|
||||
return c.Code[n]
|
||||
return OpCode(c.Code[n])
|
||||
}
|
||||
|
||||
return 0
|
||||
return STOP
|
||||
}
|
||||
|
||||
// Caller returns the caller of the contract.
|
||||
|
||||
@@ -36,6 +36,10 @@ var (
|
||||
ErrGasUintOverflow = errors.New("gas uint64 overflow")
|
||||
ErrInvalidCode = errors.New("invalid code: must not begin with 0xef")
|
||||
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")
|
||||
|
||||
// errStopToken is an internal token indicating interpreter loop termination,
|
||||
// never returned to outside callers.
|
||||
errStopToken = errors.New("stop token")
|
||||
)
|
||||
|
||||
// ErrStackUnderflow wraps an evm error when the items on the stack less
|
||||
|
||||
+1
-17
@@ -165,9 +165,6 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
|
||||
// the necessary steps to create accounts and reverses the state in case of an
|
||||
// execution error or failed value transfer.
|
||||
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, gas, nil
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
@@ -254,9 +251,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
// CallCode differs from Call in the sense that it executes the given address'
|
||||
// code with the caller as context.
|
||||
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, gas, nil
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
@@ -305,9 +299,6 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||
// DelegateCall differs from CallCode in the sense that it executes the given address'
|
||||
// code with the caller as context and the caller is set to the caller of the caller.
|
||||
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, gas, nil
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
@@ -347,9 +338,6 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||
// Opcodes that attempt to perform such modifications will result in exceptions
|
||||
// instead of performing the modifications.
|
||||
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, gas, nil
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
@@ -451,10 +439,6 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
contract := NewContract(caller, AccountRef(address), value, gas)
|
||||
contract.SetCodeOptionalHash(&address, codeAndHash)
|
||||
|
||||
if evm.Config.NoRecursion && evm.depth > 0 {
|
||||
return nil, address, gas, nil
|
||||
}
|
||||
|
||||
if evm.Config.Debug {
|
||||
if evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
|
||||
@@ -518,7 +502,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||
|
||||
// Create2 creates a new contract using code as deployment code.
|
||||
//
|
||||
// The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:]
|
||||
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
|
||||
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
||||
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||
codeAndHash := &codeAndHash{code: code}
|
||||
|
||||
@@ -247,7 +247,7 @@ func makeGasLog(n uint64) gasFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -256,7 +256,7 @@ func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
|
||||
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
|
||||
@@ -290,7 +290,7 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
|
||||
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Keccak256WordGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var _ = (*structLogMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (s StructLog) MarshalJSON() ([]byte, error) {
|
||||
type StructLog struct {
|
||||
Pc uint64 `json:"pc"`
|
||||
Op OpCode `json:"op"`
|
||||
Gas math.HexOrDecimal64 `json:"gas"`
|
||||
GasCost math.HexOrDecimal64 `json:"gasCost"`
|
||||
Memory hexutil.Bytes `json:"memory"`
|
||||
MemorySize int `json:"memSize"`
|
||||
Stack []uint256.Int `json:"stack"`
|
||||
ReturnData hexutil.Bytes `json:"returnData"`
|
||||
Storage map[common.Hash]common.Hash `json:"-"`
|
||||
Depth int `json:"depth"`
|
||||
RefundCounter uint64 `json:"refund"`
|
||||
Err error `json:"-"`
|
||||
OpName string `json:"opName"`
|
||||
ErrorString string `json:"error"`
|
||||
}
|
||||
var enc StructLog
|
||||
enc.Pc = s.Pc
|
||||
enc.Op = s.Op
|
||||
enc.Gas = math.HexOrDecimal64(s.Gas)
|
||||
enc.GasCost = math.HexOrDecimal64(s.GasCost)
|
||||
enc.Memory = s.Memory
|
||||
enc.MemorySize = s.MemorySize
|
||||
enc.Stack = s.Stack
|
||||
enc.ReturnData = s.ReturnData
|
||||
enc.Storage = s.Storage
|
||||
enc.Depth = s.Depth
|
||||
enc.RefundCounter = s.RefundCounter
|
||||
enc.Err = s.Err
|
||||
enc.OpName = s.OpName()
|
||||
enc.ErrorString = s.ErrorString()
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (s *StructLog) UnmarshalJSON(input []byte) error {
|
||||
type StructLog struct {
|
||||
Pc *uint64 `json:"pc"`
|
||||
Op *OpCode `json:"op"`
|
||||
Gas *math.HexOrDecimal64 `json:"gas"`
|
||||
GasCost *math.HexOrDecimal64 `json:"gasCost"`
|
||||
Memory *hexutil.Bytes `json:"memory"`
|
||||
MemorySize *int `json:"memSize"`
|
||||
Stack []uint256.Int `json:"stack"`
|
||||
ReturnData *hexutil.Bytes `json:"returnData"`
|
||||
Storage map[common.Hash]common.Hash `json:"-"`
|
||||
Depth *int `json:"depth"`
|
||||
RefundCounter *uint64 `json:"refund"`
|
||||
Err error `json:"-"`
|
||||
}
|
||||
var dec StructLog
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.Pc != nil {
|
||||
s.Pc = *dec.Pc
|
||||
}
|
||||
if dec.Op != nil {
|
||||
s.Op = *dec.Op
|
||||
}
|
||||
if dec.Gas != nil {
|
||||
s.Gas = uint64(*dec.Gas)
|
||||
}
|
||||
if dec.GasCost != nil {
|
||||
s.GasCost = uint64(*dec.GasCost)
|
||||
}
|
||||
if dec.Memory != nil {
|
||||
s.Memory = *dec.Memory
|
||||
}
|
||||
if dec.MemorySize != nil {
|
||||
s.MemorySize = *dec.MemorySize
|
||||
}
|
||||
if dec.Stack != nil {
|
||||
s.Stack = dec.Stack
|
||||
}
|
||||
if dec.ReturnData != nil {
|
||||
s.ReturnData = *dec.ReturnData
|
||||
}
|
||||
if dec.Storage != nil {
|
||||
s.Storage = dec.Storage
|
||||
}
|
||||
if dec.Depth != nil {
|
||||
s.Depth = *dec.Depth
|
||||
}
|
||||
if dec.RefundCounter != nil {
|
||||
s.RefundCounter = *dec.RefundCounter
|
||||
}
|
||||
if dec.Err != nil {
|
||||
s.Err = dec.Err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+47
-10
@@ -17,6 +17,8 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
@@ -231,7 +233,7 @@ func opSAR(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func opSha3(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
offset, size := scope.Stack.pop(), scope.Stack.peek()
|
||||
data := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
|
||||
@@ -514,6 +516,9 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
|
||||
}
|
||||
|
||||
func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
loc := scope.Stack.pop()
|
||||
val := scope.Stack.pop()
|
||||
interpreter.evm.StateDB.SetState(scope.Contract.Address(),
|
||||
@@ -522,23 +527,27 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
||||
}
|
||||
|
||||
func opJump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if atomic.LoadInt32(&interpreter.evm.abort) != 0 {
|
||||
return nil, errStopToken
|
||||
}
|
||||
pos := scope.Stack.pop()
|
||||
if !scope.Contract.validJumpdest(&pos) {
|
||||
return nil, ErrInvalidJump
|
||||
}
|
||||
*pc = pos.Uint64()
|
||||
*pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func opJumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if atomic.LoadInt32(&interpreter.evm.abort) != 0 {
|
||||
return nil, errStopToken
|
||||
}
|
||||
pos, cond := scope.Stack.pop(), scope.Stack.pop()
|
||||
if !cond.IsZero() {
|
||||
if !scope.Contract.validJumpdest(&pos) {
|
||||
return nil, ErrInvalidJump
|
||||
}
|
||||
*pc = pos.Uint64()
|
||||
} else {
|
||||
*pc++
|
||||
*pc = pos.Uint64() - 1 // pc will be increased by the interpreter loop
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -563,6 +572,9 @@ func opGas(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte
|
||||
}
|
||||
|
||||
func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
var (
|
||||
value = scope.Stack.pop()
|
||||
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||
@@ -598,12 +610,17 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
if suberr == ErrExecutionReverted {
|
||||
interpreter.returnData = res // set REVERT data to return data buffer
|
||||
return res, nil
|
||||
}
|
||||
interpreter.returnData = nil // clear dirty return data buffer
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
var (
|
||||
endowment = scope.Stack.pop()
|
||||
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||
@@ -634,8 +651,10 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
if suberr == ErrExecutionReverted {
|
||||
interpreter.returnData = res // set REVERT data to return data buffer
|
||||
return res, nil
|
||||
}
|
||||
interpreter.returnData = nil // clear dirty return data buffer
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -651,6 +670,9 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
|
||||
// Get the arguments from the memory.
|
||||
args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
|
||||
|
||||
if interpreter.readOnly && !value.IsZero() {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
var bigVal = big0
|
||||
//TODO: use uint256.Int instead of converting with toBig()
|
||||
// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
|
||||
@@ -674,6 +696,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
|
||||
}
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -709,6 +732,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
|
||||
}
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -737,6 +761,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
|
||||
}
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -765,6 +790,7 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
||||
}
|
||||
scope.Contract.Gas += returnGas
|
||||
|
||||
interpreter.returnData = ret
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -772,21 +798,29 @@ func opReturn(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
||||
offset, size := scope.Stack.pop(), scope.Stack.pop()
|
||||
ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
|
||||
return ret, nil
|
||||
return ret, errStopToken
|
||||
}
|
||||
|
||||
func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
offset, size := scope.Stack.pop(), scope.Stack.pop()
|
||||
ret := scope.Memory.GetPtr(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
|
||||
return ret, nil
|
||||
interpreter.returnData = ret
|
||||
return ret, ErrExecutionReverted
|
||||
}
|
||||
|
||||
func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.Code[*pc])}
|
||||
}
|
||||
|
||||
func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
return nil, nil
|
||||
return nil, errStopToken
|
||||
}
|
||||
|
||||
func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
beneficiary := scope.Stack.pop()
|
||||
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
|
||||
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
|
||||
@@ -795,7 +829,7 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||
interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
|
||||
interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil)
|
||||
}
|
||||
return nil, nil
|
||||
return nil, errStopToken
|
||||
}
|
||||
|
||||
// following functions are used by the instruction jump table
|
||||
@@ -803,6 +837,9 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||
// make log instruction function
|
||||
func makeLog(size int) executionFunc {
|
||||
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
if interpreter.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
topics := make([]common.Hash, size)
|
||||
stack := scope.Stack
|
||||
mStart, mSize := stack.pop(), stack.pop()
|
||||
|
||||
@@ -525,12 +525,14 @@ func TestOpMstore(t *testing.T) {
|
||||
mem.Resize(64)
|
||||
pc := uint64(0)
|
||||
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
|
||||
stack.pushN(*new(uint256.Int).SetBytes(common.Hex2Bytes(v)), *new(uint256.Int))
|
||||
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
|
||||
stack.push(new(uint256.Int))
|
||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
|
||||
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
|
||||
}
|
||||
stack.pushN(*new(uint256.Int).SetUint64(0x1), *new(uint256.Int))
|
||||
stack.push(new(uint256.Int).SetUint64(0x1))
|
||||
stack.push(new(uint256.Int))
|
||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
|
||||
t.Fatalf("Mstore failed to overwrite previous value")
|
||||
@@ -553,12 +555,13 @@ func BenchmarkOpMstore(bench *testing.B) {
|
||||
|
||||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
stack.pushN(*value, *memStart)
|
||||
stack.push(value)
|
||||
stack.push(memStart)
|
||||
opMstore(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkOpSHA3(bench *testing.B) {
|
||||
func BenchmarkOpKeccak256(bench *testing.B) {
|
||||
var (
|
||||
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
|
||||
stack = newstack()
|
||||
@@ -572,8 +575,9 @@ func BenchmarkOpSHA3(bench *testing.B) {
|
||||
|
||||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
stack.pushN(*uint256.NewInt(32), *start)
|
||||
opSha3(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
stack.push(uint256.NewInt(32))
|
||||
stack.push(start)
|
||||
opKeccak256(&pc, evmInterpreter, &ScopeContext{mem, stack, nil})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+49
-84
@@ -18,7 +18,6 @@ package vm
|
||||
|
||||
import (
|
||||
"hash"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
@@ -29,11 +28,10 @@ import (
|
||||
type Config struct {
|
||||
Debug bool // Enables debugging
|
||||
Tracer EVMLogger // Opcode logger
|
||||
NoRecursion bool // Disables call, callcode, delegate call and create
|
||||
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
|
||||
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
|
||||
|
||||
JumpTable [256]*operation // EVM instruction table, automatically populated if unset
|
||||
JumpTable *JumpTable // EVM instruction table, automatically populated if unset
|
||||
|
||||
ExtraEips []int // Additional EIPS that are to be enabled
|
||||
}
|
||||
@@ -68,39 +66,37 @@ type EVMInterpreter struct {
|
||||
|
||||
// NewEVMInterpreter returns a new instance of the Interpreter.
|
||||
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
|
||||
// We use the STOP instruction whether to see
|
||||
// the jump table was initialised. If it was not
|
||||
// we'll set the default jump table.
|
||||
if cfg.JumpTable[STOP] == nil {
|
||||
var jt JumpTable
|
||||
// If jump table was not initialised we set the default one.
|
||||
if cfg.JumpTable == nil {
|
||||
switch {
|
||||
case evm.chainRules.IsLondon:
|
||||
jt = londonInstructionSet
|
||||
cfg.JumpTable = &londonInstructionSet
|
||||
case evm.chainRules.IsBerlin:
|
||||
jt = berlinInstructionSet
|
||||
cfg.JumpTable = &berlinInstructionSet
|
||||
case evm.chainRules.IsIstanbul:
|
||||
jt = istanbulInstructionSet
|
||||
cfg.JumpTable = &istanbulInstructionSet
|
||||
case evm.chainRules.IsConstantinople:
|
||||
jt = constantinopleInstructionSet
|
||||
cfg.JumpTable = &constantinopleInstructionSet
|
||||
case evm.chainRules.IsByzantium:
|
||||
jt = byzantiumInstructionSet
|
||||
cfg.JumpTable = &byzantiumInstructionSet
|
||||
case evm.chainRules.IsEIP158:
|
||||
jt = spuriousDragonInstructionSet
|
||||
cfg.JumpTable = &spuriousDragonInstructionSet
|
||||
case evm.chainRules.IsEIP150:
|
||||
jt = tangerineWhistleInstructionSet
|
||||
cfg.JumpTable = &tangerineWhistleInstructionSet
|
||||
case evm.chainRules.IsHomestead:
|
||||
jt = homesteadInstructionSet
|
||||
cfg.JumpTable = &homesteadInstructionSet
|
||||
default:
|
||||
jt = frontierInstructionSet
|
||||
cfg.JumpTable = &frontierInstructionSet
|
||||
}
|
||||
for i, eip := range cfg.ExtraEips {
|
||||
if err := EnableEIP(eip, &jt); err != nil {
|
||||
copy := *cfg.JumpTable
|
||||
if err := EnableEIP(eip, ©); err != nil {
|
||||
// Disable it, so caller can check if it's activated or not
|
||||
cfg.ExtraEips = append(cfg.ExtraEips[:i], cfg.ExtraEips[i+1:]...)
|
||||
log.Error("EIP activation failed", "eip", eip, "error", err)
|
||||
}
|
||||
cfg.JumpTable = ©
|
||||
}
|
||||
cfg.JumpTable = jt
|
||||
}
|
||||
|
||||
return &EVMInterpreter{
|
||||
@@ -180,101 +176,70 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
||||
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
|
||||
// the execution of one of the operations or until the done flag is set by the
|
||||
// parent context.
|
||||
steps := 0
|
||||
for {
|
||||
steps++
|
||||
if steps%1000 == 0 && atomic.LoadInt32(&in.evm.abort) != 0 {
|
||||
break
|
||||
}
|
||||
if in.cfg.Debug {
|
||||
// Capture pre-execution values for tracing.
|
||||
logged, pcCopy, gasCopy = false, pc, contract.Gas
|
||||
}
|
||||
|
||||
// Get the operation from the jump table and validate the stack to ensure there are
|
||||
// enough stack items available to perform the operation.
|
||||
op = contract.GetOp(pc)
|
||||
operation := in.cfg.JumpTable[op]
|
||||
if operation == nil {
|
||||
return nil, &ErrInvalidOpCode{opcode: op}
|
||||
}
|
||||
cost = operation.constantGas // For tracing
|
||||
// Validate stack
|
||||
if sLen := stack.len(); sLen < operation.minStack {
|
||||
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
|
||||
} else if sLen > operation.maxStack {
|
||||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||
}
|
||||
// If the operation is valid, enforce write restrictions
|
||||
if in.readOnly && in.evm.chainRules.IsByzantium {
|
||||
// If the interpreter is operating in readonly mode, make sure no
|
||||
// state-modifying operation is performed. The 3rd stack item
|
||||
// for a call operation is the value. Transferring value from one
|
||||
// account to the others means the state is modified and should also
|
||||
// return with an error.
|
||||
if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
}
|
||||
// Static portion of gas
|
||||
cost = operation.constantGas // For tracing
|
||||
if !contract.UseGas(operation.constantGas) {
|
||||
if !contract.UseGas(cost) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
|
||||
var memorySize uint64
|
||||
// calculate the new memory size and expand the memory to fit
|
||||
// the operation
|
||||
// Memory check needs to be done prior to evaluating the dynamic gas portion,
|
||||
// to detect calculation overflows
|
||||
if operation.memorySize != nil {
|
||||
memSize, overflow := operation.memorySize(stack)
|
||||
if overflow {
|
||||
return nil, ErrGasUintOverflow
|
||||
}
|
||||
// memory is expanded in words of 32 bytes. Gas
|
||||
// is also calculated in words.
|
||||
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
|
||||
return nil, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
// Dynamic portion of gas
|
||||
// consume the gas and return an error if not enough gas is available.
|
||||
// cost is explicitly set so that the capture state defer method can get the proper cost
|
||||
if operation.dynamicGas != nil {
|
||||
// All ops with a dynamic memory usage also has a dynamic gas cost.
|
||||
var memorySize uint64
|
||||
// calculate the new memory size and expand the memory to fit
|
||||
// the operation
|
||||
// Memory check needs to be done prior to evaluating the dynamic gas portion,
|
||||
// to detect calculation overflows
|
||||
if operation.memorySize != nil {
|
||||
memSize, overflow := operation.memorySize(stack)
|
||||
if overflow {
|
||||
return nil, ErrGasUintOverflow
|
||||
}
|
||||
// memory is expanded in words of 32 bytes. Gas
|
||||
// is also calculated in words.
|
||||
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
|
||||
return nil, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
// Consume the gas and return an error if not enough gas is available.
|
||||
// cost is explicitly set so that the capture state defer method can get the proper cost
|
||||
var dynamicCost uint64
|
||||
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
|
||||
cost += dynamicCost // total cost, for debug tracing
|
||||
cost += dynamicCost // for tracing
|
||||
if err != nil || !contract.UseGas(dynamicCost) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
if memorySize > 0 {
|
||||
mem.Resize(memorySize)
|
||||
}
|
||||
}
|
||||
if memorySize > 0 {
|
||||
mem.Resize(memorySize)
|
||||
}
|
||||
|
||||
if in.cfg.Debug {
|
||||
in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
|
||||
logged = true
|
||||
}
|
||||
|
||||
// execute the operation
|
||||
res, err = operation.execute(&pc, in, callContext)
|
||||
// if the operation clears the return data (e.g. it has returning data)
|
||||
// set the last return to the result of the operation.
|
||||
if operation.returns {
|
||||
in.returnData = res
|
||||
}
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case operation.reverts:
|
||||
return res, ErrExecutionReverted
|
||||
case operation.halts:
|
||||
return res, nil
|
||||
case !operation.jumps:
|
||||
pc++
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
pc++
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
if err == errStopToken {
|
||||
err = nil // clear stop token error
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2021 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 vm
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
var loopInterruptTests = []string{
|
||||
// infinite loop using JUMP: push(2) jumpdest dup1 jump
|
||||
"60025b8056",
|
||||
// infinite loop using JUMPI: push(1) push(4) jumpdest dup2 dup2 jumpi
|
||||
"600160045b818157",
|
||||
}
|
||||
|
||||
func TestLoopInterrupt(t *testing.T) {
|
||||
address := common.BytesToAddress([]byte("contract"))
|
||||
vmctx := BlockContext{
|
||||
Transfer: func(StateDB, common.Address, common.Address, *big.Int) {},
|
||||
}
|
||||
|
||||
for i, tt := range loopInterruptTests {
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||
statedb.CreateAccount(address)
|
||||
statedb.SetCode(address, common.Hex2Bytes(tt))
|
||||
statedb.Finalise(true)
|
||||
|
||||
evm := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{})
|
||||
|
||||
errChannel := make(chan error)
|
||||
timeout := make(chan bool)
|
||||
|
||||
go func(evm *EVM) {
|
||||
_, _, err := evm.Call(AccountRef(common.Address{}), address, nil, math.MaxUint64, new(big.Int))
|
||||
errChannel <- err
|
||||
}(evm)
|
||||
|
||||
go func() {
|
||||
<-time.After(time.Second)
|
||||
timeout <- true
|
||||
}()
|
||||
|
||||
evm.Cancel()
|
||||
|
||||
select {
|
||||
case <-timeout:
|
||||
t.Errorf("test %d timed out", i)
|
||||
case err := <-errChannel:
|
||||
if err != nil {
|
||||
t.Errorf("test %d failure: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+44
-43
@@ -17,6 +17,8 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
@@ -40,12 +42,6 @@ type operation struct {
|
||||
|
||||
// memorySize returns the memory size required for the operation
|
||||
memorySize memorySizeFunc
|
||||
|
||||
halts bool // indicates whether the operation should halt further execution
|
||||
jumps bool // indicates whether the program counter should not increment
|
||||
writes bool // determines whether this a state modifying operation
|
||||
reverts bool // determines whether the operation reverts state (implicitly halts)
|
||||
returns bool // determines whether the operations sets the return data content
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -63,13 +59,31 @@ var (
|
||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||
type JumpTable [256]*operation
|
||||
|
||||
func validate(jt JumpTable) JumpTable {
|
||||
for i, op := range jt {
|
||||
if op == nil {
|
||||
panic(fmt.Sprintf("op 0x%x is not set", i))
|
||||
}
|
||||
// The interpreter has an assumption that if the memorySize function is
|
||||
// set, then the dynamicGas function is also set. This is a somewhat
|
||||
// arbitrary assumption, and can be removed if we need to -- but it
|
||||
// allows us to avoid a condition check. As long as we have that assumption
|
||||
// in there, this little sanity check prevents us from merging in a
|
||||
// change which violates it.
|
||||
if op.memorySize != nil && op.dynamicGas == nil {
|
||||
panic(fmt.Sprintf("op %v has dynamic memory but not dynamic gas", OpCode(i).String()))
|
||||
}
|
||||
}
|
||||
return jt
|
||||
}
|
||||
|
||||
// newLondonInstructionSet returns the frontier, homestead, byzantium,
|
||||
// contantinople, istanbul, petersburg, berlin and london instructions.
|
||||
func newLondonInstructionSet() JumpTable {
|
||||
instructionSet := newBerlinInstructionSet()
|
||||
enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529
|
||||
enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// newBerlinInstructionSet returns the frontier, homestead, byzantium,
|
||||
@@ -77,7 +91,7 @@ func newLondonInstructionSet() JumpTable {
|
||||
func newBerlinInstructionSet() JumpTable {
|
||||
instructionSet := newIstanbulInstructionSet()
|
||||
enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// newIstanbulInstructionSet returns the frontier, homestead, byzantium,
|
||||
@@ -89,7 +103,7 @@ func newIstanbulInstructionSet() JumpTable {
|
||||
enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884
|
||||
enable2200(&instructionSet) // Net metered SSTORE - https://eips.ethereum.org/EIPS/eip-2200
|
||||
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// newConstantinopleInstructionSet returns the frontier, homestead,
|
||||
@@ -127,10 +141,8 @@ func newConstantinopleInstructionSet() JumpTable {
|
||||
minStack: minStack(4, 1),
|
||||
maxStack: maxStack(4, 1),
|
||||
memorySize: memoryCreate2,
|
||||
writes: true,
|
||||
returns: true,
|
||||
}
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// newByzantiumInstructionSet returns the frontier, homestead and
|
||||
@@ -144,7 +156,6 @@ func newByzantiumInstructionSet() JumpTable {
|
||||
minStack: minStack(6, 1),
|
||||
maxStack: maxStack(6, 1),
|
||||
memorySize: memoryStaticCall,
|
||||
returns: true,
|
||||
}
|
||||
instructionSet[RETURNDATASIZE] = &operation{
|
||||
execute: opReturnDataSize,
|
||||
@@ -166,17 +177,15 @@ func newByzantiumInstructionSet() JumpTable {
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
memorySize: memoryRevert,
|
||||
reverts: true,
|
||||
returns: true,
|
||||
}
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// EIP 158 a.k.a Spurious Dragon
|
||||
func newSpuriousDragonInstructionSet() JumpTable {
|
||||
instructionSet := newTangerineWhistleInstructionSet()
|
||||
instructionSet[EXP].dynamicGas = gasExpEIP158
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
|
||||
}
|
||||
|
||||
@@ -190,7 +199,7 @@ func newTangerineWhistleInstructionSet() JumpTable {
|
||||
instructionSet[CALL].constantGas = params.CallGasEIP150
|
||||
instructionSet[CALLCODE].constantGas = params.CallGasEIP150
|
||||
instructionSet[DELEGATECALL].constantGas = params.CallGasEIP150
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// newHomesteadInstructionSet returns the frontier and homestead
|
||||
@@ -204,21 +213,19 @@ func newHomesteadInstructionSet() JumpTable {
|
||||
minStack: minStack(6, 1),
|
||||
maxStack: maxStack(6, 1),
|
||||
memorySize: memoryDelegateCall,
|
||||
returns: true,
|
||||
}
|
||||
return instructionSet
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
// newFrontierInstructionSet returns the frontier instructions
|
||||
// that can be executed during the frontier phase.
|
||||
func newFrontierInstructionSet() JumpTable {
|
||||
return JumpTable{
|
||||
tbl := JumpTable{
|
||||
STOP: {
|
||||
execute: opStop,
|
||||
constantGas: 0,
|
||||
minStack: minStack(0, 0),
|
||||
maxStack: maxStack(0, 0),
|
||||
halts: true,
|
||||
},
|
||||
ADD: {
|
||||
execute: opAdd,
|
||||
@@ -352,13 +359,13 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(2, 1),
|
||||
maxStack: maxStack(2, 1),
|
||||
},
|
||||
SHA3: {
|
||||
execute: opSha3,
|
||||
constantGas: params.Sha3Gas,
|
||||
dynamicGas: gasSha3,
|
||||
KECCAK256: {
|
||||
execute: opKeccak256,
|
||||
constantGas: params.Keccak256Gas,
|
||||
dynamicGas: gasKeccak256,
|
||||
minStack: minStack(2, 1),
|
||||
maxStack: maxStack(2, 1),
|
||||
memorySize: memorySha3,
|
||||
memorySize: memoryKeccak256,
|
||||
},
|
||||
ADDRESS: {
|
||||
execute: opAddress,
|
||||
@@ -521,21 +528,18 @@ func newFrontierInstructionSet() JumpTable {
|
||||
dynamicGas: gasSStore,
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
writes: true,
|
||||
},
|
||||
JUMP: {
|
||||
execute: opJump,
|
||||
constantGas: GasMidStep,
|
||||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(1, 0),
|
||||
jumps: true,
|
||||
},
|
||||
JUMPI: {
|
||||
execute: opJumpi,
|
||||
constantGas: GasSlowStep,
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
jumps: true,
|
||||
},
|
||||
PC: {
|
||||
execute: opPc,
|
||||
@@ -951,7 +955,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
memorySize: memoryLog,
|
||||
writes: true,
|
||||
},
|
||||
LOG1: {
|
||||
execute: makeLog(1),
|
||||
@@ -959,7 +962,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(3, 0),
|
||||
maxStack: maxStack(3, 0),
|
||||
memorySize: memoryLog,
|
||||
writes: true,
|
||||
},
|
||||
LOG2: {
|
||||
execute: makeLog(2),
|
||||
@@ -967,7 +969,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(4, 0),
|
||||
maxStack: maxStack(4, 0),
|
||||
memorySize: memoryLog,
|
||||
writes: true,
|
||||
},
|
||||
LOG3: {
|
||||
execute: makeLog(3),
|
||||
@@ -975,7 +976,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(5, 0),
|
||||
maxStack: maxStack(5, 0),
|
||||
memorySize: memoryLog,
|
||||
writes: true,
|
||||
},
|
||||
LOG4: {
|
||||
execute: makeLog(4),
|
||||
@@ -983,7 +983,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(6, 0),
|
||||
maxStack: maxStack(6, 0),
|
||||
memorySize: memoryLog,
|
||||
writes: true,
|
||||
},
|
||||
CREATE: {
|
||||
execute: opCreate,
|
||||
@@ -992,8 +991,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(3, 1),
|
||||
maxStack: maxStack(3, 1),
|
||||
memorySize: memoryCreate,
|
||||
writes: true,
|
||||
returns: true,
|
||||
},
|
||||
CALL: {
|
||||
execute: opCall,
|
||||
@@ -1002,7 +999,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(7, 1),
|
||||
maxStack: maxStack(7, 1),
|
||||
memorySize: memoryCall,
|
||||
returns: true,
|
||||
},
|
||||
CALLCODE: {
|
||||
execute: opCallCode,
|
||||
@@ -1011,7 +1007,6 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(7, 1),
|
||||
maxStack: maxStack(7, 1),
|
||||
memorySize: memoryCall,
|
||||
returns: true,
|
||||
},
|
||||
RETURN: {
|
||||
execute: opReturn,
|
||||
@@ -1019,15 +1014,21 @@ func newFrontierInstructionSet() JumpTable {
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
memorySize: memoryReturn,
|
||||
halts: true,
|
||||
},
|
||||
SELFDESTRUCT: {
|
||||
execute: opSuicide,
|
||||
execute: opSelfdestruct,
|
||||
dynamicGas: gasSelfdestruct,
|
||||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(1, 0),
|
||||
halts: true,
|
||||
writes: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Fill all unassigned slots with opUndefined.
|
||||
for i, entry := range tbl {
|
||||
if entry == nil {
|
||||
tbl[i] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)}
|
||||
}
|
||||
}
|
||||
|
||||
return validate(tbl)
|
||||
}
|
||||
|
||||
+1
-323
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// Copyright 2021 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
|
||||
@@ -17,87 +17,12 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// Storage represents a contract's storage.
|
||||
type Storage map[common.Hash]common.Hash
|
||||
|
||||
// Copy duplicates the current storage.
|
||||
func (s Storage) Copy() Storage {
|
||||
cpy := make(Storage)
|
||||
for key, value := range s {
|
||||
cpy[key] = value
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// LogConfig are the configuration options for structured logger the EVM
|
||||
type LogConfig struct {
|
||||
EnableMemory bool // enable memory capture
|
||||
DisableStack bool // disable stack capture
|
||||
DisableStorage bool // disable storage capture
|
||||
EnableReturnData bool // enable return data capture
|
||||
Debug bool // print output during capture end
|
||||
Limit int // maximum length of output, but zero means unlimited
|
||||
// Chain overrides, can be used to execute a trace using future fork rules
|
||||
Overrides *params.ChainConfig `json:"overrides,omitempty"`
|
||||
}
|
||||
|
||||
//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
|
||||
|
||||
// StructLog is emitted to the EVM each cycle and lists information about the current internal state
|
||||
// prior to the execution of the statement.
|
||||
type StructLog struct {
|
||||
Pc uint64 `json:"pc"`
|
||||
Op OpCode `json:"op"`
|
||||
Gas uint64 `json:"gas"`
|
||||
GasCost uint64 `json:"gasCost"`
|
||||
Memory []byte `json:"memory"`
|
||||
MemorySize int `json:"memSize"`
|
||||
Stack []uint256.Int `json:"stack"`
|
||||
ReturnData []byte `json:"returnData"`
|
||||
Storage map[common.Hash]common.Hash `json:"-"`
|
||||
Depth int `json:"depth"`
|
||||
RefundCounter uint64 `json:"refund"`
|
||||
Err error `json:"-"`
|
||||
}
|
||||
|
||||
// overrides for gencodec
|
||||
type structLogMarshaling struct {
|
||||
Gas math.HexOrDecimal64
|
||||
GasCost math.HexOrDecimal64
|
||||
Memory hexutil.Bytes
|
||||
ReturnData hexutil.Bytes
|
||||
OpName string `json:"opName"` // adds call to OpName() in MarshalJSON
|
||||
ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON
|
||||
}
|
||||
|
||||
// OpName formats the operand name in a human-readable format.
|
||||
func (s *StructLog) OpName() string {
|
||||
return s.Op.String()
|
||||
}
|
||||
|
||||
// ErrorString formats the log's error as a string.
|
||||
func (s *StructLog) ErrorString() string {
|
||||
if s.Err != nil {
|
||||
return s.Err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// EVMLogger is used to collect execution traces from an EVM transaction
|
||||
// execution. CaptureState is called for each step of the VM with the
|
||||
// current VM state.
|
||||
@@ -111,250 +36,3 @@ type EVMLogger interface {
|
||||
CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
|
||||
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
|
||||
}
|
||||
|
||||
// StructLogger is an EVM state logger and implements EVMLogger.
|
||||
//
|
||||
// StructLogger can capture state based on the given Log configuration and also keeps
|
||||
// a track record of modified storage which is used in reporting snapshots of the
|
||||
// contract their storage.
|
||||
type StructLogger struct {
|
||||
cfg LogConfig
|
||||
env *EVM
|
||||
|
||||
storage map[common.Address]Storage
|
||||
logs []StructLog
|
||||
output []byte
|
||||
err error
|
||||
}
|
||||
|
||||
// NewStructLogger returns a new logger
|
||||
func NewStructLogger(cfg *LogConfig) *StructLogger {
|
||||
logger := &StructLogger{
|
||||
storage: make(map[common.Address]Storage),
|
||||
}
|
||||
if cfg != nil {
|
||||
logger.cfg = *cfg
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// Reset clears the data held by the logger.
|
||||
func (l *StructLogger) Reset() {
|
||||
l.storage = make(map[common.Address]Storage)
|
||||
l.output = make([]byte, 0)
|
||||
l.logs = l.logs[:0]
|
||||
l.err = nil
|
||||
}
|
||||
|
||||
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
|
||||
func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
l.env = env
|
||||
}
|
||||
|
||||
// CaptureState logs a new structured log message and pushes it out to the environment
|
||||
//
|
||||
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
|
||||
func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
|
||||
memory := scope.Memory
|
||||
stack := scope.Stack
|
||||
contract := scope.Contract
|
||||
// check if already accumulated the specified number of logs
|
||||
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
|
||||
return
|
||||
}
|
||||
// Copy a snapshot of the current memory state to a new buffer
|
||||
var mem []byte
|
||||
if l.cfg.EnableMemory {
|
||||
mem = make([]byte, len(memory.Data()))
|
||||
copy(mem, memory.Data())
|
||||
}
|
||||
// Copy a snapshot of the current stack state to a new buffer
|
||||
var stck []uint256.Int
|
||||
if !l.cfg.DisableStack {
|
||||
stck = make([]uint256.Int, len(stack.Data()))
|
||||
for i, item := range stack.Data() {
|
||||
stck[i] = item
|
||||
}
|
||||
}
|
||||
// Copy a snapshot of the current storage to a new container
|
||||
var storage Storage
|
||||
if !l.cfg.DisableStorage && (op == SLOAD || op == SSTORE) {
|
||||
// initialise new changed values storage container for this contract
|
||||
// if not present.
|
||||
if l.storage[contract.Address()] == nil {
|
||||
l.storage[contract.Address()] = make(Storage)
|
||||
}
|
||||
// capture SLOAD opcodes and record the read entry in the local storage
|
||||
if op == SLOAD && stack.len() >= 1 {
|
||||
var (
|
||||
address = common.Hash(stack.data[stack.len()-1].Bytes32())
|
||||
value = l.env.StateDB.GetState(contract.Address(), address)
|
||||
)
|
||||
l.storage[contract.Address()][address] = value
|
||||
storage = l.storage[contract.Address()].Copy()
|
||||
} else if op == SSTORE && stack.len() >= 2 {
|
||||
// capture SSTORE opcodes and record the written entry in the local storage.
|
||||
var (
|
||||
value = common.Hash(stack.data[stack.len()-2].Bytes32())
|
||||
address = common.Hash(stack.data[stack.len()-1].Bytes32())
|
||||
)
|
||||
l.storage[contract.Address()][address] = value
|
||||
storage = l.storage[contract.Address()].Copy()
|
||||
}
|
||||
}
|
||||
var rdata []byte
|
||||
if l.cfg.EnableReturnData {
|
||||
rdata = make([]byte, len(rData))
|
||||
copy(rdata, rData)
|
||||
}
|
||||
// create a new snapshot of the EVM.
|
||||
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err}
|
||||
l.logs = append(l.logs, log)
|
||||
}
|
||||
|
||||
// CaptureFault implements the EVMLogger interface to trace an execution fault
|
||||
// while running an opcode.
|
||||
func (l *StructLogger) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
|
||||
l.output = output
|
||||
l.err = err
|
||||
if l.cfg.Debug {
|
||||
fmt.Printf("0x%x\n", output)
|
||||
if err != nil {
|
||||
fmt.Printf(" error: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *StructLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
||||
// StructLogs returns the captured log entries.
|
||||
func (l *StructLogger) StructLogs() []StructLog { return l.logs }
|
||||
|
||||
// Error returns the VM error captured by the trace.
|
||||
func (l *StructLogger) Error() error { return l.err }
|
||||
|
||||
// Output returns the VM return value captured by the trace.
|
||||
func (l *StructLogger) Output() []byte { return l.output }
|
||||
|
||||
// WriteTrace writes a formatted trace to the given writer
|
||||
func WriteTrace(writer io.Writer, logs []StructLog) {
|
||||
for _, log := range logs {
|
||||
fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost)
|
||||
if log.Err != nil {
|
||||
fmt.Fprintf(writer, " ERROR: %v", log.Err)
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
|
||||
if len(log.Stack) > 0 {
|
||||
fmt.Fprintln(writer, "Stack:")
|
||||
for i := len(log.Stack) - 1; i >= 0; i-- {
|
||||
fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex())
|
||||
}
|
||||
}
|
||||
if len(log.Memory) > 0 {
|
||||
fmt.Fprintln(writer, "Memory:")
|
||||
fmt.Fprint(writer, hex.Dump(log.Memory))
|
||||
}
|
||||
if len(log.Storage) > 0 {
|
||||
fmt.Fprintln(writer, "Storage:")
|
||||
for h, item := range log.Storage {
|
||||
fmt.Fprintf(writer, "%x: %x\n", h, item)
|
||||
}
|
||||
}
|
||||
if len(log.ReturnData) > 0 {
|
||||
fmt.Fprintln(writer, "ReturnData:")
|
||||
fmt.Fprint(writer, hex.Dump(log.ReturnData))
|
||||
}
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteLogs writes vm logs in a readable format to the given writer
|
||||
func WriteLogs(writer io.Writer, logs []*types.Log) {
|
||||
for _, log := range logs {
|
||||
fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex)
|
||||
|
||||
for i, topic := range log.Topics {
|
||||
fmt.Fprintf(writer, "%08d %x\n", i, topic)
|
||||
}
|
||||
|
||||
fmt.Fprint(writer, hex.Dump(log.Data))
|
||||
fmt.Fprintln(writer)
|
||||
}
|
||||
}
|
||||
|
||||
type mdLogger struct {
|
||||
out io.Writer
|
||||
cfg *LogConfig
|
||||
env *EVM
|
||||
}
|
||||
|
||||
// NewMarkdownLogger creates a logger which outputs information in a format adapted
|
||||
// for human readability, and is also a valid markdown table
|
||||
func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger {
|
||||
l := &mdLogger{out: writer, cfg: cfg}
|
||||
if l.cfg == nil {
|
||||
l.cfg = &LogConfig{}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
t.env = env
|
||||
if !create {
|
||||
fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
|
||||
from.String(), to.String(),
|
||||
input, gas, value)
|
||||
} else {
|
||||
fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n",
|
||||
from.String(), to.String(),
|
||||
input, gas, value)
|
||||
}
|
||||
|
||||
fmt.Fprintf(t.out, `
|
||||
| Pc | Op | Cost | Stack | RStack | Refund |
|
||||
|-------|-------------|------|-----------|-----------|---------|
|
||||
`)
|
||||
}
|
||||
|
||||
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
|
||||
func (t *mdLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
|
||||
stack := scope.Stack
|
||||
fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
|
||||
|
||||
if !t.cfg.DisableStack {
|
||||
// format stack
|
||||
var a []string
|
||||
for _, elem := range stack.data {
|
||||
a = append(a, elem.Hex())
|
||||
}
|
||||
b := fmt.Sprintf("[%v]", strings.Join(a, ","))
|
||||
fmt.Fprintf(t.out, "%10v |", b)
|
||||
}
|
||||
fmt.Fprintf(t.out, "%10v |", t.env.StateDB.GetRefund())
|
||||
fmt.Fprintln(t.out, "")
|
||||
if err != nil {
|
||||
fmt.Fprintf(t.out, "Error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
|
||||
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) {
|
||||
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
|
||||
output, gasUsed, err)
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright 2017 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 vm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
)
|
||||
|
||||
type JSONLogger struct {
|
||||
encoder *json.Encoder
|
||||
cfg *LogConfig
|
||||
env *EVM
|
||||
}
|
||||
|
||||
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
|
||||
// into the provided stream.
|
||||
func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
|
||||
l := &JSONLogger{encoder: json.NewEncoder(writer), cfg: cfg}
|
||||
if l.cfg == nil {
|
||||
l.cfg = &LogConfig{}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureStart(env *EVM, from, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
|
||||
l.env = env
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureFault(uint64, OpCode, uint64, uint64, *ScopeContext, int, error) {}
|
||||
|
||||
// CaptureState outputs state information on the logger.
|
||||
func (l *JSONLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
|
||||
memory := scope.Memory
|
||||
stack := scope.Stack
|
||||
|
||||
log := StructLog{
|
||||
Pc: pc,
|
||||
Op: op,
|
||||
Gas: gas,
|
||||
GasCost: cost,
|
||||
MemorySize: memory.Len(),
|
||||
Depth: depth,
|
||||
RefundCounter: l.env.StateDB.GetRefund(),
|
||||
Err: err,
|
||||
}
|
||||
if l.cfg.EnableMemory {
|
||||
log.Memory = memory.Data()
|
||||
}
|
||||
if !l.cfg.DisableStack {
|
||||
log.Stack = stack.data
|
||||
}
|
||||
if l.cfg.EnableReturnData {
|
||||
log.ReturnData = rData
|
||||
}
|
||||
l.encoder.Encode(log)
|
||||
}
|
||||
|
||||
// CaptureEnd is triggered at end of execution.
|
||||
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
|
||||
type endLog struct {
|
||||
Output string `json:"output"`
|
||||
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||
Time time.Duration `json:"time"`
|
||||
Err string `json:"error,omitempty"`
|
||||
}
|
||||
var errMsg string
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, errMsg})
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright 2016 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 vm
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
type dummyContractRef struct {
|
||||
calledForEach bool
|
||||
}
|
||||
|
||||
func (dummyContractRef) Address() common.Address { return common.Address{} }
|
||||
func (dummyContractRef) Value() *big.Int { return new(big.Int) }
|
||||
func (dummyContractRef) SetCode(common.Hash, []byte) {}
|
||||
func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) {
|
||||
d.calledForEach = true
|
||||
}
|
||||
func (d *dummyContractRef) SubBalance(amount *big.Int) {}
|
||||
func (d *dummyContractRef) AddBalance(amount *big.Int) {}
|
||||
func (d *dummyContractRef) SetBalance(*big.Int) {}
|
||||
func (d *dummyContractRef) SetNonce(uint64) {}
|
||||
func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
|
||||
|
||||
type dummyStatedb struct {
|
||||
state.StateDB
|
||||
}
|
||||
|
||||
func (*dummyStatedb) GetRefund() uint64 { return 1337 }
|
||||
|
||||
func TestStoreCapture(t *testing.T) {
|
||||
var (
|
||||
env = NewEVM(BlockContext{}, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{})
|
||||
logger = NewStructLogger(nil)
|
||||
contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 0)
|
||||
scope = &ScopeContext{
|
||||
Memory: NewMemory(),
|
||||
Stack: newstack(),
|
||||
Contract: contract,
|
||||
}
|
||||
)
|
||||
scope.Stack.push(uint256.NewInt(1))
|
||||
scope.Stack.push(new(uint256.Int))
|
||||
var index common.Hash
|
||||
logger.CaptureStart(env, common.Address{}, contract.Address(), false, nil, 0, nil)
|
||||
logger.CaptureState(0, SSTORE, 0, 0, scope, nil, 0, nil)
|
||||
if len(logger.storage[contract.Address()]) == 0 {
|
||||
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(),
|
||||
len(logger.storage[contract.Address()]))
|
||||
}
|
||||
exp := common.BigToHash(big.NewInt(1))
|
||||
if logger.storage[contract.Address()][index] != exp {
|
||||
t.Errorf("expected %x, got %x", exp, logger.storage[contract.Address()][index])
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package vm
|
||||
|
||||
func memorySha3(stack *Stack) (uint64, bool) {
|
||||
func memoryKeccak256(stack *Stack) (uint64, bool) {
|
||||
return calcMemSize64(stack.Back(0), stack.Back(1))
|
||||
}
|
||||
|
||||
|
||||
+6
-19
@@ -32,11 +32,6 @@ func (op OpCode) IsPush() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsStaticJump specifies if an opcode is JUMP.
|
||||
func (op OpCode) IsStaticJump() bool {
|
||||
return op == JUMP
|
||||
}
|
||||
|
||||
// 0x0 range - arithmetic ops.
|
||||
const (
|
||||
STOP OpCode = 0x0
|
||||
@@ -70,7 +65,7 @@ const (
|
||||
SHR OpCode = 0x1c
|
||||
SAR OpCode = 0x1d
|
||||
|
||||
SHA3 OpCode = 0x20
|
||||
KECCAK256 OpCode = 0x20
|
||||
)
|
||||
|
||||
// 0x30 range - closure state.
|
||||
@@ -207,13 +202,6 @@ const (
|
||||
LOG4
|
||||
)
|
||||
|
||||
// unofficial opcodes used for parsing.
|
||||
const (
|
||||
PUSH OpCode = 0xb0 + iota
|
||||
DUP
|
||||
SWAP
|
||||
)
|
||||
|
||||
// 0xf0 range - closures.
|
||||
const (
|
||||
CREATE OpCode = 0xf0
|
||||
@@ -225,6 +213,7 @@ const (
|
||||
|
||||
STATICCALL OpCode = 0xfa
|
||||
REVERT OpCode = 0xfd
|
||||
INVALID OpCode = 0xfe
|
||||
SELFDESTRUCT OpCode = 0xff
|
||||
)
|
||||
|
||||
@@ -261,7 +250,7 @@ var opCodeToString = map[OpCode]string{
|
||||
MULMOD: "MULMOD",
|
||||
|
||||
// 0x20 range - crypto.
|
||||
SHA3: "SHA3",
|
||||
KECCAK256: "KECCAK256",
|
||||
|
||||
// 0x30 range - closure state.
|
||||
ADDRESS: "ADDRESS",
|
||||
@@ -390,11 +379,8 @@ var opCodeToString = map[OpCode]string{
|
||||
CREATE2: "CREATE2",
|
||||
STATICCALL: "STATICCALL",
|
||||
REVERT: "REVERT",
|
||||
INVALID: "INVALID",
|
||||
SELFDESTRUCT: "SELFDESTRUCT",
|
||||
|
||||
PUSH: "PUSH",
|
||||
DUP: "DUP",
|
||||
SWAP: "SWAP",
|
||||
}
|
||||
|
||||
func (op OpCode) String() string {
|
||||
@@ -433,7 +419,7 @@ var stringToOp = map[string]OpCode{
|
||||
"SAR": SAR,
|
||||
"ADDMOD": ADDMOD,
|
||||
"MULMOD": MULMOD,
|
||||
"SHA3": SHA3,
|
||||
"KECCAK256": KECCAK256,
|
||||
"ADDRESS": ADDRESS,
|
||||
"BALANCE": BALANCE,
|
||||
"ORIGIN": ORIGIN,
|
||||
@@ -548,6 +534,7 @@ var stringToOp = map[string]OpCode{
|
||||
"RETURN": RETURN,
|
||||
"CALLCODE": CALLCODE,
|
||||
"REVERT": REVERT,
|
||||
"INVALID": INVALID,
|
||||
"SELFDESTRUCT": SELFDESTRUCT,
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
||||
// force-load js tracers to trigger registration
|
||||
@@ -326,7 +327,7 @@ func TestBlockhash(t *testing.T) {
|
||||
}
|
||||
|
||||
type stepCounter struct {
|
||||
inner *vm.JSONLogger
|
||||
inner *logger.JSONLogger
|
||||
steps int
|
||||
}
|
||||
|
||||
@@ -493,7 +494,7 @@ func BenchmarkSimpleLoop(b *testing.B) {
|
||||
byte(vm.JUMP),
|
||||
}
|
||||
|
||||
//tracer := vm.NewJSONLogger(nil, os.Stdout)
|
||||
//tracer := logger.NewJSONLogger(nil, os.Stdout)
|
||||
//Execute(loopingCode, nil, &Config{
|
||||
// EVMConfig: vm.Config{
|
||||
// Debug: true,
|
||||
@@ -536,7 +537,7 @@ func TestEip2929Cases(t *testing.T) {
|
||||
Execute(code, nil, &Config{
|
||||
EVMConfig: vm.Config{
|
||||
Debug: true,
|
||||
Tracer: vm.NewMarkdownLogger(nil, os.Stdout),
|
||||
Tracer: logger.NewMarkdownLogger(nil, os.Stdout),
|
||||
ExtraEips: []int{2929},
|
||||
},
|
||||
})
|
||||
@@ -686,7 +687,7 @@ func TestColdAccountAccessCost(t *testing.T) {
|
||||
want: 7600,
|
||||
},
|
||||
} {
|
||||
tracer := vm.NewStructLogger(nil)
|
||||
tracer := logger.NewStructLogger(nil)
|
||||
Execute(tc.code, nil, &Config{
|
||||
EVMConfig: vm.Config{
|
||||
Debug: true,
|
||||
|
||||
@@ -54,10 +54,6 @@ func (st *Stack) push(d *uint256.Int) {
|
||||
// NOTE push limit (1024) is checked in baseCheck
|
||||
st.data = append(st.data, *d)
|
||||
}
|
||||
func (st *Stack) pushN(ds ...uint256.Int) {
|
||||
// FIXME: Is there a way to pass args by pointers.
|
||||
st.data = append(st.data, ds...)
|
||||
}
|
||||
|
||||
func (st *Stack) pop() (ret uint256.Int) {
|
||||
ret = st.data[len(st.data)-1]
|
||||
|
||||
Reference in New Issue
Block a user