forked from cerc-io/ipld-eth-server
Update dependencies
- uses newer version of go-ethereum required for go1.11
This commit is contained in:
+6
-15
@@ -17,7 +17,6 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
@@ -52,7 +51,7 @@ func NewCompiler(debug bool) *Compiler {
|
||||
// the compiler.
|
||||
//
|
||||
// feed is the first pass in the compile stage as it
|
||||
// collect the used labels in the program and keeps a
|
||||
// collects the used labels in the program and keeps a
|
||||
// program counter which is used to determine the locations
|
||||
// of the jump dests. The labels can than be used in the
|
||||
// second stage to push labels and determine the right
|
||||
@@ -114,15 +113,15 @@ func (c *Compiler) Compile() (string, []error) {
|
||||
}
|
||||
|
||||
// next returns the next token and increments the
|
||||
// posititon.
|
||||
// position.
|
||||
func (c *Compiler) next() token {
|
||||
token := c.tokens[c.pos]
|
||||
c.pos++
|
||||
return token
|
||||
}
|
||||
|
||||
// compile line compiles a single line instruction e.g.
|
||||
// "push 1", "jump @labal".
|
||||
// compileLine compiles a single line instruction e.g.
|
||||
// "push 1", "jump @label".
|
||||
func (c *Compiler) compileLine() error {
|
||||
n := c.next()
|
||||
if n.typ != lineStart {
|
||||
@@ -237,19 +236,16 @@ func (c *Compiler) pushBin(v interface{}) {
|
||||
// isPush returns whether the string op is either any of
|
||||
// push(N).
|
||||
func isPush(op string) bool {
|
||||
return op == "push"
|
||||
return strings.ToUpper(op) == "PUSH"
|
||||
}
|
||||
|
||||
// isJump returns whether the string op is jump(i)
|
||||
func isJump(op string) bool {
|
||||
return op == "jumpi" || op == "jump"
|
||||
return strings.ToUpper(op) == "JUMPI" || strings.ToUpper(op) == "JUMP"
|
||||
}
|
||||
|
||||
// toBinary converts text to a vm.OpCode
|
||||
func toBinary(text string) vm.OpCode {
|
||||
if isPush(text) {
|
||||
text = "push1"
|
||||
}
|
||||
return vm.StringToOp(strings.ToUpper(text))
|
||||
}
|
||||
|
||||
@@ -264,11 +260,6 @@ func (err compileError) Error() string {
|
||||
return fmt.Sprintf("%d syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want)
|
||||
}
|
||||
|
||||
var (
|
||||
errExpBol = errors.New("expected beginning of line")
|
||||
errExpElementOrLabel = errors.New("expected beginning of line")
|
||||
)
|
||||
|
||||
func compileErr(c token, got, want string) error {
|
||||
return compileError{
|
||||
got: got,
|
||||
|
||||
+2
-2
@@ -48,7 +48,7 @@ const (
|
||||
lineEnd // emitted when a line ends
|
||||
invalidStatement // any invalid statement
|
||||
element // any element during element parsing
|
||||
label // label is emitted when a labal is found
|
||||
label // label is emitted when a label is found
|
||||
labelDef // label definition is emitted when a new label is found
|
||||
number // number is emitted when a number is found
|
||||
stringValue // stringValue is emitted when a string has been found
|
||||
@@ -242,7 +242,7 @@ func lexLabel(l *lexer) stateFn {
|
||||
}
|
||||
|
||||
// lexInsideString lexes the inside of a string until
|
||||
// until the state function finds the closing quote.
|
||||
// the state function finds the closing quote.
|
||||
// It returns the lex text state function.
|
||||
func lexInsideString(l *lexer) stateFn {
|
||||
if l.acceptRunUntil('"') {
|
||||
|
||||
+13
-10
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"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"
|
||||
@@ -110,7 +111,8 @@ func init() {
|
||||
func genTxRing(naccounts int) func(int, *BlockGen) {
|
||||
from := 0
|
||||
return func(i int, gen *BlockGen) {
|
||||
gas := CalcGasLimit(gen.PrevBlock(i - 1))
|
||||
block := gen.PrevBlock(i - 1)
|
||||
gas := CalcGasLimit(block, block.GasLimit(), block.GasLimit())
|
||||
for {
|
||||
gas -= params.TxGas
|
||||
if gas < params.TxGas {
|
||||
@@ -148,7 +150,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
|
||||
// Create the database in memory or in a temporary directory.
|
||||
var db ethdb.Database
|
||||
if !disk {
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
} else {
|
||||
dir, err := ioutil.TempDir("", "eth-core-bench")
|
||||
if err != nil {
|
||||
@@ -234,13 +236,15 @@ func makeChainForBench(db ethdb.Database, full bool, count uint64) {
|
||||
ReceiptHash: types.EmptyRootHash,
|
||||
}
|
||||
hash = header.Hash()
|
||||
WriteHeader(db, header)
|
||||
WriteCanonicalHash(db, hash, n)
|
||||
WriteTd(db, hash, n, big.NewInt(int64(n+1)))
|
||||
|
||||
rawdb.WriteHeader(db, header)
|
||||
rawdb.WriteCanonicalHash(db, hash, n)
|
||||
rawdb.WriteTd(db, hash, n, big.NewInt(int64(n+1)))
|
||||
|
||||
if full || n == 0 {
|
||||
block := types.NewBlockWithHeader(header)
|
||||
WriteBody(db, hash, n, block.Body())
|
||||
WriteBlockReceipts(db, hash, n, nil)
|
||||
rawdb.WriteBody(db, hash, n, block.Body())
|
||||
rawdb.WriteReceipts(db, hash, n, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,11 +296,10 @@ func benchReadChain(b *testing.B, full bool, count uint64) {
|
||||
header := chain.GetHeaderByNumber(n)
|
||||
if full {
|
||||
hash := header.Hash()
|
||||
GetBody(db, hash, n)
|
||||
GetBlockReceipts(db, hash, n)
|
||||
rawdb.ReadBody(db, hash, n)
|
||||
rawdb.ReadReceipts(db, hash, n)
|
||||
}
|
||||
}
|
||||
|
||||
chain.Stop()
|
||||
db.Close()
|
||||
}
|
||||
|
||||
+15
-9
@@ -45,7 +45,7 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
|
||||
return validator
|
||||
}
|
||||
|
||||
// ValidateBody validates the given block's uncles and verifies the the block
|
||||
// ValidateBody validates the given block's uncles and verifies the block
|
||||
// header's transaction and uncle roots. The headers are assumed to be already
|
||||
// validated at this point.
|
||||
func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||
@@ -101,9 +101,11 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalcGasLimit computes the gas limit of the next block after parent.
|
||||
// This is miner strategy, not consensus protocol.
|
||||
func CalcGasLimit(parent *types.Block) uint64 {
|
||||
// CalcGasLimit computes the gas limit of the next block after parent. It aims
|
||||
// to keep the baseline gas above the provided floor, and increase it towards the
|
||||
// ceil if the blocks are full. If the ceil is exceeded, it will always decrease
|
||||
// the gas allowance.
|
||||
func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 {
|
||||
// contrib = (parentGasUsed * 3 / 2) / 1024
|
||||
contrib := (parent.GasUsed() + parent.GasUsed()/2) / params.GasLimitBoundDivisor
|
||||
|
||||
@@ -121,12 +123,16 @@ func CalcGasLimit(parent *types.Block) uint64 {
|
||||
if limit < params.MinGasLimit {
|
||||
limit = params.MinGasLimit
|
||||
}
|
||||
// however, if we're now below the target (TargetGasLimit) we increase the
|
||||
// limit as much as we can (parentGasLimit / 1024 -1)
|
||||
if limit < params.TargetGasLimit {
|
||||
// If we're outside our allowed gas range, we try to hone towards them
|
||||
if limit < gasFloor {
|
||||
limit = parent.GasLimit() + decay
|
||||
if limit > params.TargetGasLimit {
|
||||
limit = params.TargetGasLimit
|
||||
if limit > gasFloor {
|
||||
limit = gasFloor
|
||||
}
|
||||
} else if limit > gasCeil {
|
||||
limit = parent.GasLimit() - decay
|
||||
if limit < gasCeil {
|
||||
limit = gasCeil
|
||||
}
|
||||
}
|
||||
return limit
|
||||
|
||||
+3
-3
@@ -32,7 +32,7 @@ import (
|
||||
func TestHeaderVerification(t *testing.T) {
|
||||
// Create a simple chain to verify
|
||||
var (
|
||||
testdb, _ = ethdb.NewMemDatabase()
|
||||
testdb = ethdb.NewMemDatabase()
|
||||
gspec = &Genesis{Config: params.TestChainConfig}
|
||||
genesis = gspec.MustCommit(testdb)
|
||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
|
||||
@@ -84,7 +84,7 @@ func TestHeaderConcurrentVerification32(t *testing.T) { testHeaderConcurrentVeri
|
||||
func testHeaderConcurrentVerification(t *testing.T, threads int) {
|
||||
// Create a simple chain to verify
|
||||
var (
|
||||
testdb, _ = ethdb.NewMemDatabase()
|
||||
testdb = ethdb.NewMemDatabase()
|
||||
gspec = &Genesis{Config: params.TestChainConfig}
|
||||
genesis = gspec.MustCommit(testdb)
|
||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
|
||||
@@ -156,7 +156,7 @@ func TestHeaderConcurrentAbortion32(t *testing.T) { testHeaderConcurrentAbortion
|
||||
func testHeaderConcurrentAbortion(t *testing.T, threads int) {
|
||||
// Create a simple chain to verify
|
||||
var (
|
||||
testdb, _ = ethdb.NewMemDatabase()
|
||||
testdb = ethdb.NewMemDatabase()
|
||||
gspec = &Genesis{Config: params.TestChainConfig}
|
||||
genesis = gspec.MustCommit(testdb)
|
||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 1024, nil)
|
||||
|
||||
+140
-116
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"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"
|
||||
@@ -202,7 +203,7 @@ func (bc *BlockChain) getProcInterrupt() bool {
|
||||
// assumes that the chain manager mutex is held.
|
||||
func (bc *BlockChain) loadLastState() error {
|
||||
// Restore the last known head block
|
||||
head := GetHeadBlockHash(bc.db)
|
||||
head := rawdb.ReadHeadBlockHash(bc.db)
|
||||
if head == (common.Hash{}) {
|
||||
// Corrupt or empty database, init from scratch
|
||||
log.Warn("Empty database, resetting chain")
|
||||
@@ -228,7 +229,7 @@ func (bc *BlockChain) loadLastState() error {
|
||||
|
||||
// Restore the last known head header
|
||||
currentHeader := currentBlock.Header()
|
||||
if head := GetHeadHeaderHash(bc.db); head != (common.Hash{}) {
|
||||
if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) {
|
||||
if header := bc.GetHeaderByHash(head); header != nil {
|
||||
currentHeader = header
|
||||
}
|
||||
@@ -237,7 +238,7 @@ func (bc *BlockChain) loadLastState() error {
|
||||
|
||||
// Restore the last known head fast block
|
||||
bc.currentFastBlock.Store(currentBlock)
|
||||
if head := GetHeadFastBlockHash(bc.db); head != (common.Hash{}) {
|
||||
if head := rawdb.ReadHeadFastBlockHash(bc.db); head != (common.Hash{}) {
|
||||
if block := bc.GetBlockByHash(head); block != nil {
|
||||
bc.currentFastBlock.Store(block)
|
||||
}
|
||||
@@ -268,8 +269,8 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||
defer bc.mu.Unlock()
|
||||
|
||||
// Rewind the header chain, deleting all block bodies until then
|
||||
delFn := func(hash common.Hash, num uint64) {
|
||||
DeleteBody(bc.db, hash, num)
|
||||
delFn := func(db rawdb.DatabaseDeleter, hash common.Hash, num uint64) {
|
||||
rawdb.DeleteBody(db, hash, num)
|
||||
}
|
||||
bc.hc.SetHead(head, delFn)
|
||||
currentHeader := bc.hc.CurrentHeader()
|
||||
@@ -303,12 +304,10 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||
}
|
||||
currentBlock := bc.CurrentBlock()
|
||||
currentFastBlock := bc.CurrentFastBlock()
|
||||
if err := WriteHeadBlockHash(bc.db, currentBlock.Hash()); err != nil {
|
||||
log.Crit("Failed to reset head full block", "err", err)
|
||||
}
|
||||
if err := WriteHeadFastBlockHash(bc.db, currentFastBlock.Hash()); err != nil {
|
||||
log.Crit("Failed to reset head fast block", "err", err)
|
||||
}
|
||||
|
||||
rawdb.WriteHeadBlockHash(bc.db, currentBlock.Hash())
|
||||
rawdb.WriteHeadFastBlockHash(bc.db, currentFastBlock.Hash())
|
||||
|
||||
return bc.loadLastState()
|
||||
}
|
||||
|
||||
@@ -406,9 +405,8 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
|
||||
if err := bc.hc.WriteTd(genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()); err != nil {
|
||||
log.Crit("Failed to write genesis block TD", "err", err)
|
||||
}
|
||||
if err := WriteBlock(bc.db, genesis); err != nil {
|
||||
log.Crit("Failed to write genesis block", "err", err)
|
||||
}
|
||||
rawdb.WriteBlock(bc.db, genesis)
|
||||
|
||||
bc.genesisBlock = genesis
|
||||
bc.insert(bc.genesisBlock)
|
||||
bc.currentBlock.Store(bc.genesisBlock)
|
||||
@@ -452,15 +450,19 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
|
||||
}
|
||||
log.Info("Exporting batch of blocks", "count", last-first+1)
|
||||
|
||||
start, reported := time.Now(), time.Now()
|
||||
for nr := first; nr <= last; nr++ {
|
||||
block := bc.GetBlockByNumber(nr)
|
||||
if block == nil {
|
||||
return fmt.Errorf("export failed on #%d: not found", nr)
|
||||
}
|
||||
|
||||
if err := block.EncodeRLP(w); err != nil {
|
||||
return err
|
||||
}
|
||||
if time.Since(reported) >= statsReportLimit {
|
||||
log.Info("Exporting blocks", "exported", block.NumberU64()-first, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
reported = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -474,24 +476,19 @@ 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) insert(block *types.Block) {
|
||||
// If the block is on a side chain or an unknown one, force other heads onto it too
|
||||
updateHeads := GetCanonicalHash(bc.db, block.NumberU64()) != block.Hash()
|
||||
updateHeads := rawdb.ReadCanonicalHash(bc.db, block.NumberU64()) != block.Hash()
|
||||
|
||||
// Add the block to the canonical chain number scheme and mark as the head
|
||||
if err := WriteCanonicalHash(bc.db, block.Hash(), block.NumberU64()); err != nil {
|
||||
log.Crit("Failed to insert block number", "err", err)
|
||||
}
|
||||
if err := WriteHeadBlockHash(bc.db, block.Hash()); err != nil {
|
||||
log.Crit("Failed to insert head block hash", "err", err)
|
||||
}
|
||||
rawdb.WriteCanonicalHash(bc.db, block.Hash(), block.NumberU64())
|
||||
rawdb.WriteHeadBlockHash(bc.db, block.Hash())
|
||||
|
||||
bc.currentBlock.Store(block)
|
||||
|
||||
// If the block is better than our head or is on a different chain, force update heads
|
||||
if updateHeads {
|
||||
bc.hc.SetCurrentHeader(block.Header())
|
||||
rawdb.WriteHeadFastBlockHash(bc.db, block.Hash())
|
||||
|
||||
if err := WriteHeadFastBlockHash(bc.db, block.Hash()); err != nil {
|
||||
log.Crit("Failed to insert head fast block hash", "err", err)
|
||||
}
|
||||
bc.currentFastBlock.Store(block)
|
||||
}
|
||||
}
|
||||
@@ -509,7 +506,11 @@ func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {
|
||||
body := cached.(*types.Body)
|
||||
return body
|
||||
}
|
||||
body := GetBody(bc.db, hash, bc.hc.GetBlockNumber(hash))
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
body := rawdb.ReadBody(bc.db, hash, *number)
|
||||
if body == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -525,7 +526,11 @@ func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue {
|
||||
if cached, ok := bc.bodyRLPCache.Get(hash); ok {
|
||||
return cached.(rlp.RawValue)
|
||||
}
|
||||
body := GetBodyRLP(bc.db, hash, bc.hc.GetBlockNumber(hash))
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
body := rawdb.ReadBodyRLP(bc.db, hash, *number)
|
||||
if len(body) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -539,8 +544,7 @@ func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool {
|
||||
if bc.blockCache.Contains(hash) {
|
||||
return true
|
||||
}
|
||||
ok, _ := bc.db.Has(blockBodyKey(hash, number))
|
||||
return ok
|
||||
return rawdb.HasBody(bc.db, hash, number)
|
||||
}
|
||||
|
||||
// HasState checks if state trie is fully present in the database or not.
|
||||
@@ -567,7 +571,7 @@ func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
|
||||
if block, ok := bc.blockCache.Get(hash); ok {
|
||||
return block.(*types.Block)
|
||||
}
|
||||
block := GetBlock(bc.db, hash, number)
|
||||
block := rawdb.ReadBlock(bc.db, hash, number)
|
||||
if block == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -578,13 +582,17 @@ func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
|
||||
|
||||
// GetBlockByHash retrieves a block from the database by hash, caching it if found.
|
||||
func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block {
|
||||
return bc.GetBlock(hash, bc.hc.GetBlockNumber(hash))
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
return bc.GetBlock(hash, *number)
|
||||
}
|
||||
|
||||
// GetBlockByNumber retrieves a block from the database by number, caching it
|
||||
// (associated with its hash) if found.
|
||||
func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block {
|
||||
hash := GetCanonicalHash(bc.db, number)
|
||||
hash := rawdb.ReadCanonicalHash(bc.db, number)
|
||||
if hash == (common.Hash{}) {
|
||||
return nil
|
||||
}
|
||||
@@ -593,21 +601,28 @@ func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block {
|
||||
|
||||
// GetReceiptsByHash retrieves the receipts for all transactions in a given block.
|
||||
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
|
||||
return GetBlockReceipts(bc.db, hash, GetBlockNumber(bc.db, hash))
|
||||
number := rawdb.ReadHeaderNumber(bc.db, hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
return rawdb.ReadReceipts(bc.db, hash, *number)
|
||||
}
|
||||
|
||||
// GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors.
|
||||
// [deprecated by eth/62]
|
||||
func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*types.Block) {
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
block := bc.GetBlock(hash, number)
|
||||
block := bc.GetBlock(hash, *number)
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
blocks = append(blocks, block)
|
||||
hash = block.ParentHash()
|
||||
number--
|
||||
*number--
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -661,9 +676,9 @@ func (bc *BlockChain) Stop() {
|
||||
}
|
||||
}
|
||||
for !bc.triegc.Empty() {
|
||||
triedb.Dereference(bc.triegc.PopItem().(common.Hash), common.Hash{})
|
||||
triedb.Dereference(bc.triegc.PopItem().(common.Hash))
|
||||
}
|
||||
if size := triedb.Size(); size != 0 {
|
||||
if size, _ := triedb.Size(); size != 0 {
|
||||
log.Error("Dangling trie nodes after full cleanup")
|
||||
}
|
||||
}
|
||||
@@ -712,21 +727,24 @@ func (bc *BlockChain) Rollback(chain []common.Hash) {
|
||||
if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock.Hash() == hash {
|
||||
newFastBlock := bc.GetBlock(currentFastBlock.ParentHash(), currentFastBlock.NumberU64()-1)
|
||||
bc.currentFastBlock.Store(newFastBlock)
|
||||
WriteHeadFastBlockHash(bc.db, newFastBlock.Hash())
|
||||
rawdb.WriteHeadFastBlockHash(bc.db, newFastBlock.Hash())
|
||||
}
|
||||
if currentBlock := bc.CurrentBlock(); currentBlock.Hash() == hash {
|
||||
newBlock := bc.GetBlock(currentBlock.ParentHash(), currentBlock.NumberU64()-1)
|
||||
bc.currentBlock.Store(newBlock)
|
||||
WriteHeadBlockHash(bc.db, newBlock.Hash())
|
||||
rawdb.WriteHeadBlockHash(bc.db, newBlock.Hash())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetReceiptsData computes all the non-consensus fields of the receipts
|
||||
func SetReceiptsData(config *params.ChainConfig, block *types.Block, receipts types.Receipts) {
|
||||
func SetReceiptsData(config *params.ChainConfig, block *types.Block, receipts types.Receipts) error {
|
||||
signer := types.MakeSigner(config, block.Number())
|
||||
|
||||
transactions, logIndex := block.Transactions(), uint(0)
|
||||
if len(transactions) != len(receipts) {
|
||||
return errors.New("transaction and receipt count mismatch")
|
||||
}
|
||||
|
||||
for j := 0; j < len(receipts); j++ {
|
||||
// The transaction hash can be retrieved from the transaction itself
|
||||
@@ -754,6 +772,7 @@ func SetReceiptsData(config *params.ChainConfig, block *types.Block, receipts ty
|
||||
logIndex++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertReceiptChain attempts to complete an already existing header chain with
|
||||
@@ -794,17 +813,14 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
|
||||
continue
|
||||
}
|
||||
// Compute all the non-consensus fields of the receipts
|
||||
SetReceiptsData(bc.chainConfig, block, receipts)
|
||||
if err := SetReceiptsData(bc.chainConfig, block, receipts); err != nil {
|
||||
return i, fmt.Errorf("failed to set receipts data: %v", err)
|
||||
}
|
||||
// Write all the data out into the database
|
||||
if err := WriteBody(batch, block.Hash(), block.NumberU64(), block.Body()); err != nil {
|
||||
return i, fmt.Errorf("failed to write block body: %v", err)
|
||||
}
|
||||
if err := WriteBlockReceipts(batch, block.Hash(), block.NumberU64(), receipts); err != nil {
|
||||
return i, fmt.Errorf("failed to write block receipts: %v", err)
|
||||
}
|
||||
if err := WriteTxLookupEntries(batch, block); err != nil {
|
||||
return i, fmt.Errorf("failed to write lookup metadata: %v", err)
|
||||
}
|
||||
rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body())
|
||||
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
|
||||
rawdb.WriteTxLookupEntries(batch, block)
|
||||
|
||||
stats.processed++
|
||||
|
||||
if batch.ValueSize() >= ethdb.IdealBatchSize {
|
||||
@@ -828,9 +844,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
|
||||
if td := bc.GetTd(head.Hash(), head.NumberU64()); td != nil { // Rewind may have occurred, skip in that case
|
||||
currentFastBlock := bc.CurrentFastBlock()
|
||||
if bc.GetTd(currentFastBlock.Hash(), currentFastBlock.NumberU64()).Cmp(td) < 0 {
|
||||
if err := WriteHeadFastBlockHash(bc.db, head.Hash()); err != nil {
|
||||
log.Crit("Failed to update head fast block hash", "err", err)
|
||||
}
|
||||
rawdb.WriteHeadFastBlockHash(bc.db, head.Hash())
|
||||
bc.currentFastBlock.Store(head)
|
||||
}
|
||||
}
|
||||
@@ -858,9 +872,8 @@ func (bc *BlockChain) WriteBlockWithoutState(block *types.Block, td *big.Int) (e
|
||||
if err := bc.hc.WriteTd(block.Hash(), block.NumberU64(), td); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := WriteBlock(bc.db, block); err != nil {
|
||||
return err
|
||||
}
|
||||
rawdb.WriteBlock(bc.db, block)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -886,11 +899,8 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
|
||||
if err := bc.hc.WriteTd(block.Hash(), block.NumberU64(), externTd); err != nil {
|
||||
return NonStatTy, err
|
||||
}
|
||||
// Write other block data using a batch.
|
||||
batch := bc.db.NewBatch()
|
||||
if err := WriteBlock(batch, block); err != nil {
|
||||
return NonStatTy, err
|
||||
}
|
||||
rawdb.WriteBlock(bc.db, block)
|
||||
|
||||
root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number()))
|
||||
if err != nil {
|
||||
return NonStatTy, err
|
||||
@@ -908,33 +918,29 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
|
||||
bc.triegc.Push(root, -float32(block.NumberU64()))
|
||||
|
||||
if current := block.NumberU64(); current > triesInMemory {
|
||||
// If we exceeded our memory allowance, flush matured singleton nodes to disk
|
||||
var (
|
||||
nodes, imgs = triedb.Size()
|
||||
limit = common.StorageSize(bc.cacheConfig.TrieNodeLimit) * 1024 * 1024
|
||||
)
|
||||
if nodes > limit || imgs > 4*1024*1024 {
|
||||
triedb.Cap(limit - ethdb.IdealBatchSize)
|
||||
}
|
||||
// Find the next state trie we need to commit
|
||||
header := bc.GetHeaderByNumber(current - triesInMemory)
|
||||
chosen := header.Number.Uint64()
|
||||
|
||||
// Only write to disk if we exceeded our memory allowance *and* also have at
|
||||
// least a given number of tries gapped.
|
||||
var (
|
||||
size = triedb.Size()
|
||||
limit = common.StorageSize(bc.cacheConfig.TrieNodeLimit) * 1024 * 1024
|
||||
)
|
||||
if size > limit || bc.gcproc > bc.cacheConfig.TrieTimeLimit {
|
||||
// If we exceeded out time allowance, flush an entire trie to disk
|
||||
if bc.gcproc > bc.cacheConfig.TrieTimeLimit {
|
||||
// If we're exceeding limits but haven't reached a large enough memory gap,
|
||||
// warn the user that the system is becoming unstable.
|
||||
if chosen < lastWrite+triesInMemory {
|
||||
switch {
|
||||
case size >= 2*limit:
|
||||
log.Warn("State memory usage too high, committing", "size", size, "limit", limit, "optimum", float64(chosen-lastWrite)/triesInMemory)
|
||||
case bc.gcproc >= 2*bc.cacheConfig.TrieTimeLimit:
|
||||
log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", bc.cacheConfig.TrieTimeLimit, "optimum", float64(chosen-lastWrite)/triesInMemory)
|
||||
}
|
||||
}
|
||||
// If optimum or critical limits reached, write to disk
|
||||
if chosen >= lastWrite+triesInMemory || size >= 2*limit || bc.gcproc >= 2*bc.cacheConfig.TrieTimeLimit {
|
||||
triedb.Commit(header.Root, true)
|
||||
lastWrite = chosen
|
||||
bc.gcproc = 0
|
||||
if chosen < lastWrite+triesInMemory && bc.gcproc >= 2*bc.cacheConfig.TrieTimeLimit {
|
||||
log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", bc.cacheConfig.TrieTimeLimit, "optimum", float64(chosen-lastWrite)/triesInMemory)
|
||||
}
|
||||
// Flush an entire trie and restart the counters
|
||||
triedb.Commit(header.Root, true)
|
||||
lastWrite = chosen
|
||||
bc.gcproc = 0
|
||||
}
|
||||
// Garbage collect anything below our required write retention
|
||||
for !bc.triegc.Empty() {
|
||||
@@ -943,13 +949,15 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
|
||||
bc.triegc.Push(root, number)
|
||||
break
|
||||
}
|
||||
triedb.Dereference(root.(common.Hash), common.Hash{})
|
||||
triedb.Dereference(root.(common.Hash))
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := WriteBlockReceipts(batch, block.Hash(), block.NumberU64(), receipts); err != nil {
|
||||
return NonStatTy, err
|
||||
}
|
||||
|
||||
// Write other block data using a batch.
|
||||
batch := bc.db.NewBatch()
|
||||
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
|
||||
|
||||
// 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
|
||||
@@ -966,14 +974,10 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
|
||||
return NonStatTy, err
|
||||
}
|
||||
}
|
||||
// Write the positional metadata for transaction and receipt lookups
|
||||
if err := WriteTxLookupEntries(batch, block); err != nil {
|
||||
return NonStatTy, err
|
||||
}
|
||||
// Write hash preimages
|
||||
if err := WritePreimages(bc.db, block.NumberU64(), state.Preimages()); err != nil {
|
||||
return NonStatTy, err
|
||||
}
|
||||
// Write the positional metadata for transaction/receipt lookups and preimages
|
||||
rawdb.WriteTxLookupEntries(batch, block)
|
||||
rawdb.WritePreimages(batch, block.NumberU64(), state.Preimages())
|
||||
|
||||
status = CanonStatTy
|
||||
} else {
|
||||
status = SideStatTy
|
||||
@@ -1006,10 +1010,14 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
|
||||
// only reason this method exists as a separate one is to make locking cleaner
|
||||
// with deferred statements.
|
||||
func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*types.Log, error) {
|
||||
// Sanity check that we have something meaningful to import
|
||||
if len(chain) == 0 {
|
||||
return 0, nil, nil, nil
|
||||
}
|
||||
// Do a sanity check that the provided chain is actually ordered and linked
|
||||
for i := 1; i < len(chain); i++ {
|
||||
if chain[i].NumberU64() != chain[i-1].NumberU64()+1 || chain[i].ParentHash() != chain[i-1].Hash() {
|
||||
// Chain broke ancestry, log a messge (programming error) and skip insertion
|
||||
// Chain broke ancestry, log a message (programming error) and skip insertion
|
||||
log.Error("Non contiguous block insert", "number", chain[i].Number(), "hash", chain[i].Hash(),
|
||||
"parent", chain[i].ParentHash(), "prevnumber", chain[i-1].Number(), "prevhash", chain[i-1].Hash())
|
||||
|
||||
@@ -1044,6 +1052,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
|
||||
abort, results := bc.engine.VerifyHeaders(bc, headers, seals)
|
||||
defer close(abort)
|
||||
|
||||
// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss)
|
||||
senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain)
|
||||
|
||||
// Iterate over the blocks and insert when the verifier permits
|
||||
for i, block := range chain {
|
||||
// If the chain is terminating, stop processing blocks
|
||||
@@ -1178,7 +1189,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
|
||||
}
|
||||
stats.processed++
|
||||
stats.usedGas += usedGas
|
||||
stats.report(chain, i, bc.stateCache.TrieDB().Size())
|
||||
|
||||
cache, _ := bc.stateCache.TrieDB().Size()
|
||||
stats.report(chain, i, cache)
|
||||
}
|
||||
// Append a single chain head event if we've progressed the chain
|
||||
if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() {
|
||||
@@ -1195,8 +1208,8 @@ type insertStats struct {
|
||||
startTime mclock.AbsTime
|
||||
}
|
||||
|
||||
// statsReportLimit is the time limit during import after which we always print
|
||||
// out progress. This avoids the user wondering what's going on.
|
||||
// statsReportLimit is the time limit during import and export after which we
|
||||
// always print out progress. This avoids the user wondering what's going on.
|
||||
const statsReportLimit = 8 * time.Second
|
||||
|
||||
// report prints statistics if some number of blocks have been processed
|
||||
@@ -1250,9 +1263,13 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
// collectLogs collects the logs that were generated during the
|
||||
// processing of the block that corresponds with the given hash.
|
||||
// These logs are later announced as deleted.
|
||||
collectLogs = func(h common.Hash) {
|
||||
collectLogs = func(hash common.Hash) {
|
||||
// Coalesce logs and set 'Removed'.
|
||||
receipts := GetBlockReceipts(bc.db, h, bc.hc.GetBlockNumber(h))
|
||||
number := bc.hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return
|
||||
}
|
||||
receipts := rawdb.ReadReceipts(bc.db, hash, *number)
|
||||
for _, receipt := range receipts {
|
||||
for _, log := range receipt.Logs {
|
||||
del := *log
|
||||
@@ -1321,18 +1338,19 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
||||
// insert the block in the canonical way, re-writing history
|
||||
bc.insert(newChain[i])
|
||||
// write lookup entries for hash based transaction/receipt searches
|
||||
if err := WriteTxLookupEntries(bc.db, newChain[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
rawdb.WriteTxLookupEntries(bc.db, newChain[i])
|
||||
addedTxs = append(addedTxs, newChain[i].Transactions()...)
|
||||
}
|
||||
// calculate the difference between deleted and added transactions
|
||||
diff := types.TxDifference(deletedTxs, addedTxs)
|
||||
// When transactions get deleted from the database that means the
|
||||
// receipts that were created in the fork must also be deleted
|
||||
batch := bc.db.NewBatch()
|
||||
for _, tx := range diff {
|
||||
DeleteTxLookupEntry(bc.db, tx.Hash())
|
||||
rawdb.DeleteTxLookupEntry(batch, tx.Hash())
|
||||
}
|
||||
batch.Write()
|
||||
|
||||
if len(deletedLogs) > 0 {
|
||||
go bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs})
|
||||
}
|
||||
@@ -1382,27 +1400,21 @@ func (bc *BlockChain) update() {
|
||||
}
|
||||
}
|
||||
|
||||
// BadBlockArgs represents the entries in the list returned when bad blocks are queried.
|
||||
type BadBlockArgs struct {
|
||||
Hash common.Hash `json:"hash"`
|
||||
Header *types.Header `json:"header"`
|
||||
}
|
||||
|
||||
// BadBlocks returns a list of the last 'bad blocks' that the client has seen on the network
|
||||
func (bc *BlockChain) BadBlocks() ([]BadBlockArgs, error) {
|
||||
headers := make([]BadBlockArgs, 0, bc.badBlocks.Len())
|
||||
func (bc *BlockChain) BadBlocks() []*types.Block {
|
||||
blocks := make([]*types.Block, 0, bc.badBlocks.Len())
|
||||
for _, hash := range bc.badBlocks.Keys() {
|
||||
if hdr, exist := bc.badBlocks.Peek(hash); exist {
|
||||
header := hdr.(*types.Header)
|
||||
headers = append(headers, BadBlockArgs{header.Hash(), header})
|
||||
if blk, exist := bc.badBlocks.Peek(hash); exist {
|
||||
block := blk.(*types.Block)
|
||||
blocks = append(blocks, block)
|
||||
}
|
||||
}
|
||||
return headers, nil
|
||||
return blocks
|
||||
}
|
||||
|
||||
// addBadBlock adds a bad block to the bad-block LRU cache
|
||||
func (bc *BlockChain) addBadBlock(block *types.Block) {
|
||||
bc.badBlocks.Add(block.Header().Hash(), block.Header())
|
||||
bc.badBlocks.Add(block.Hash(), block)
|
||||
}
|
||||
|
||||
// reportBlock logs a bad block error.
|
||||
@@ -1520,6 +1532,18 @@ func (bc *BlockChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []com
|
||||
return bc.hc.GetBlockHashesFromHash(hash, max)
|
||||
}
|
||||
|
||||
// GetAncestor retrieves the Nth ancestor of a given block. It assumes that either the given block or
|
||||
// a close ancestor of it is canonical. maxNonCanonical points to a downwards counter limiting the
|
||||
// number of blocks to be individually checked before we reach the canonical chain.
|
||||
//
|
||||
// Note: ancestor == 0 returns the same block, 1 returns its parent and so on.
|
||||
func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) {
|
||||
bc.chainmu.Lock()
|
||||
defer bc.chainmu.Unlock()
|
||||
|
||||
return bc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical)
|
||||
}
|
||||
|
||||
// GetHeaderByNumber retrieves a block header from the database by number,
|
||||
// caching it (associated with its hash) if found.
|
||||
func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
|
||||
|
||||
+201
-56
@@ -25,7 +25,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"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"
|
||||
@@ -34,6 +36,39 @@ import (
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// So we can deterministically seed different blockchains
|
||||
var (
|
||||
canonicalSeed = 1
|
||||
forkSeed = 2
|
||||
)
|
||||
|
||||
// newCanonical creates a chain database, and injects a deterministic canonical
|
||||
// chain. Depending on the full flag, if creates either a full block chain or a
|
||||
// header only chain.
|
||||
func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *BlockChain, error) {
|
||||
var (
|
||||
db = ethdb.NewMemDatabase()
|
||||
genesis = new(Genesis).MustCommit(db)
|
||||
)
|
||||
|
||||
// Initialize a fresh chain with only a genesis block
|
||||
blockchain, _ := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{})
|
||||
// Create and inject the requested chain
|
||||
if n == 0 {
|
||||
return db, blockchain, nil
|
||||
}
|
||||
if full {
|
||||
// Full block-chain requested
|
||||
blocks := makeBlockChain(genesis, n, engine, db, canonicalSeed)
|
||||
_, err := blockchain.InsertChain(blocks)
|
||||
return db, blockchain, err
|
||||
}
|
||||
// Header-only chain requested
|
||||
headers := makeHeaderChain(genesis.Header(), n, engine, db, canonicalSeed)
|
||||
_, err := blockchain.InsertHeaderChain(headers, 1)
|
||||
return db, blockchain, err
|
||||
}
|
||||
|
||||
// Test fork of length N starting from block i
|
||||
func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
|
||||
// Copy old chain up to #i into a new db
|
||||
@@ -128,8 +163,8 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
|
||||
return err
|
||||
}
|
||||
blockchain.mu.Lock()
|
||||
WriteTd(blockchain.db, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTdByHash(block.ParentHash())))
|
||||
WriteBlock(blockchain.db, block)
|
||||
rawdb.WriteTd(blockchain.db, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTdByHash(block.ParentHash())))
|
||||
rawdb.WriteBlock(blockchain.db, block)
|
||||
statedb.Commit(false)
|
||||
blockchain.mu.Unlock()
|
||||
}
|
||||
@@ -146,8 +181,8 @@ func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error
|
||||
}
|
||||
// Manually insert the header into the database, but don't reorganise (allows subsequent testing)
|
||||
blockchain.mu.Lock()
|
||||
WriteTd(blockchain.db, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, blockchain.GetTdByHash(header.ParentHash)))
|
||||
WriteHeader(blockchain.db, header)
|
||||
rawdb.WriteTd(blockchain.db, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, blockchain.GetTdByHash(header.ParentHash)))
|
||||
rawdb.WriteHeader(blockchain.db, header)
|
||||
blockchain.mu.Unlock()
|
||||
}
|
||||
return nil
|
||||
@@ -173,7 +208,7 @@ func TestLastBlock(t *testing.T) {
|
||||
if _, err := blockchain.InsertChain(blocks); err != nil {
|
||||
t.Fatalf("Failed to insert block: %v", err)
|
||||
}
|
||||
if blocks[len(blocks)-1].Hash() != GetHeadBlockHash(blockchain.db) {
|
||||
if blocks[len(blocks)-1].Hash() != rawdb.ReadHeadBlockHash(blockchain.db) {
|
||||
t.Fatalf("Write/Get HeadBlockHash failed")
|
||||
}
|
||||
}
|
||||
@@ -568,16 +603,16 @@ func testInsertNonceError(t *testing.T, full bool) {
|
||||
func TestFastVsFullChains(t *testing.T) {
|
||||
// Configure and generate a sample block chain
|
||||
var (
|
||||
gendb, _ = ethdb.NewMemDatabase()
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
funds = big.NewInt(1000000000)
|
||||
gspec = &Genesis{
|
||||
gendb = ethdb.NewMemDatabase()
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
funds = big.NewInt(1000000000)
|
||||
gspec = &Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: GenesisAlloc{address: {Balance: funds}},
|
||||
}
|
||||
genesis = gspec.MustCommit(gendb)
|
||||
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
||||
signer = types.NewEIP155Signer(gspec.Config.ChainID)
|
||||
)
|
||||
blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, 1024, func(i int, block *BlockGen) {
|
||||
block.SetCoinbase(common.Address{0x00})
|
||||
@@ -598,7 +633,7 @@ func TestFastVsFullChains(t *testing.T) {
|
||||
}
|
||||
})
|
||||
// Import the chain as an archive node for the comparison baseline
|
||||
archiveDb, _ := ethdb.NewMemDatabase()
|
||||
archiveDb := ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(archiveDb)
|
||||
archive, _ := NewBlockChain(archiveDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||
defer archive.Stop()
|
||||
@@ -607,7 +642,7 @@ func TestFastVsFullChains(t *testing.T) {
|
||||
t.Fatalf("failed to process block %d: %v", n, err)
|
||||
}
|
||||
// Fast import the chain as a non-archive node to test
|
||||
fastDb, _ := ethdb.NewMemDatabase()
|
||||
fastDb := ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(fastDb)
|
||||
fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||
defer fast.Stop()
|
||||
@@ -639,13 +674,13 @@ func TestFastVsFullChains(t *testing.T) {
|
||||
} else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(ablock.Uncles()) {
|
||||
t.Errorf("block #%d [%x]: uncles mismatch: have %v, want %v", num, hash, fblock.Uncles(), ablock.Uncles())
|
||||
}
|
||||
if freceipts, areceipts := GetBlockReceipts(fastDb, hash, GetBlockNumber(fastDb, hash)), GetBlockReceipts(archiveDb, hash, GetBlockNumber(archiveDb, hash)); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) {
|
||||
if freceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash)), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash)); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) {
|
||||
t.Errorf("block #%d [%x]: receipts mismatch: have %v, want %v", num, hash, freceipts, areceipts)
|
||||
}
|
||||
}
|
||||
// Check that the canonical chains are the same between the databases
|
||||
for i := 0; i < len(blocks)+1; i++ {
|
||||
if fhash, ahash := GetCanonicalHash(fastDb, uint64(i)), GetCanonicalHash(archiveDb, uint64(i)); fhash != ahash {
|
||||
if fhash, ahash := rawdb.ReadCanonicalHash(fastDb, uint64(i)), rawdb.ReadCanonicalHash(archiveDb, uint64(i)); fhash != ahash {
|
||||
t.Errorf("block #%d: canonical hash mismatch: have %v, want %v", i, fhash, ahash)
|
||||
}
|
||||
}
|
||||
@@ -656,12 +691,12 @@ func TestFastVsFullChains(t *testing.T) {
|
||||
func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||
// Configure and generate a sample block chain
|
||||
var (
|
||||
gendb, _ = ethdb.NewMemDatabase()
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
funds = big.NewInt(1000000000)
|
||||
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}}
|
||||
genesis = gspec.MustCommit(gendb)
|
||||
gendb = ethdb.NewMemDatabase()
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
funds = big.NewInt(1000000000)
|
||||
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}}
|
||||
genesis = gspec.MustCommit(gendb)
|
||||
)
|
||||
height := uint64(1024)
|
||||
blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), nil)
|
||||
@@ -684,7 +719,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// Import the chain as an archive node and ensure all pointers are updated
|
||||
archiveDb, _ := ethdb.NewMemDatabase()
|
||||
archiveDb := ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(archiveDb)
|
||||
|
||||
archive, _ := NewBlockChain(archiveDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||
@@ -698,7 +733,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||
assert(t, "archive", archive, height/2, height/2, height/2)
|
||||
|
||||
// Import the chain as a non-archive node and ensure all pointers are updated
|
||||
fastDb, _ := ethdb.NewMemDatabase()
|
||||
fastDb := ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(fastDb)
|
||||
fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||
defer fast.Stop()
|
||||
@@ -718,7 +753,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||
assert(t, "fast", fast, height/2, height/2, 0)
|
||||
|
||||
// Import the chain as a light node and ensure all pointers are updated
|
||||
lightDb, _ := ethdb.NewMemDatabase()
|
||||
lightDb := ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(lightDb)
|
||||
|
||||
light, _ := NewBlockChain(lightDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||
@@ -741,7 +776,7 @@ func TestChainTxReorgs(t *testing.T) {
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
gspec = &Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
GasLimit: 3141592,
|
||||
@@ -752,7 +787,7 @@ func TestChainTxReorgs(t *testing.T) {
|
||||
},
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
||||
signer = types.NewEIP155Signer(gspec.Config.ChainID)
|
||||
)
|
||||
|
||||
// Create two transactions shared between the chains:
|
||||
@@ -821,28 +856,28 @@ func TestChainTxReorgs(t *testing.T) {
|
||||
|
||||
// removed tx
|
||||
for i, tx := range (types.Transactions{pastDrop, freshDrop}) {
|
||||
if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn != nil {
|
||||
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn != nil {
|
||||
t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn)
|
||||
}
|
||||
if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt != nil {
|
||||
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash()); rcpt != nil {
|
||||
t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt)
|
||||
}
|
||||
}
|
||||
// added tx
|
||||
for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) {
|
||||
if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn == nil {
|
||||
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil {
|
||||
t.Errorf("add %d: expected tx to be found", i)
|
||||
}
|
||||
if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt == nil {
|
||||
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash()); rcpt == nil {
|
||||
t.Errorf("add %d: expected receipt to be found", i)
|
||||
}
|
||||
}
|
||||
// shared tx
|
||||
for i, tx := range (types.Transactions{postponed, swapped}) {
|
||||
if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn == nil {
|
||||
if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil {
|
||||
t.Errorf("share %d: expected tx to be found", i)
|
||||
}
|
||||
if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt == nil {
|
||||
if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash()); rcpt == nil {
|
||||
t.Errorf("share %d: expected receipt to be found", i)
|
||||
}
|
||||
}
|
||||
@@ -853,12 +888,12 @@ func TestLogReorgs(t *testing.T) {
|
||||
var (
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
// this code generates a log
|
||||
code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
|
||||
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
|
||||
genesis = gspec.MustCommit(db)
|
||||
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
||||
signer = types.NewEIP155Signer(gspec.Config.ChainID)
|
||||
)
|
||||
|
||||
blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||
@@ -897,7 +932,7 @@ func TestLogReorgs(t *testing.T) {
|
||||
|
||||
func TestReorgSideEvent(t *testing.T) {
|
||||
var (
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
gspec = &Genesis{
|
||||
@@ -905,7 +940,7 @@ func TestReorgSideEvent(t *testing.T) {
|
||||
Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}},
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
||||
signer = types.NewEIP155Signer(gspec.Config.ChainID)
|
||||
)
|
||||
|
||||
blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||
@@ -997,14 +1032,14 @@ func TestCanonicalBlockRetrieval(t *testing.T) {
|
||||
|
||||
// try to retrieve a block by its canonical hash and see if the block data can be retrieved.
|
||||
for {
|
||||
ch := GetCanonicalHash(blockchain.db, block.NumberU64())
|
||||
ch := rawdb.ReadCanonicalHash(blockchain.db, block.NumberU64())
|
||||
if ch == (common.Hash{}) {
|
||||
continue // busy wait for canonical hash to be written
|
||||
}
|
||||
if ch != block.Hash() {
|
||||
t.Fatalf("unknown canonical hash, want %s, got %s", block.Hash().Hex(), ch.Hex())
|
||||
}
|
||||
fb := GetBlock(blockchain.db, ch, block.NumberU64())
|
||||
fb := rawdb.ReadBlock(blockchain.db, ch, block.NumberU64())
|
||||
if fb == nil {
|
||||
t.Fatalf("unable to retrieve block %d for canonical hash: %s", block.NumberU64(), ch.Hex())
|
||||
}
|
||||
@@ -1025,13 +1060,13 @@ func TestCanonicalBlockRetrieval(t *testing.T) {
|
||||
func TestEIP155Transition(t *testing.T) {
|
||||
// Configure and generate a sample block chain
|
||||
var (
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
funds = big.NewInt(1000000000)
|
||||
deleteAddr = common.Address{1}
|
||||
gspec = &Genesis{
|
||||
Config: ¶ms.ChainConfig{ChainId: big.NewInt(1), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)},
|
||||
Config: ¶ms.ChainConfig{ChainID: big.NewInt(1), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)},
|
||||
Alloc: GenesisAlloc{address: {Balance: funds}, deleteAddr: {Balance: new(big.Int)}},
|
||||
}
|
||||
genesis = gspec.MustCommit(db)
|
||||
@@ -1062,7 +1097,7 @@ func TestEIP155Transition(t *testing.T) {
|
||||
}
|
||||
block.AddTx(tx)
|
||||
|
||||
tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainId))
|
||||
tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainID))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1074,7 +1109,7 @@ func TestEIP155Transition(t *testing.T) {
|
||||
}
|
||||
block.AddTx(tx)
|
||||
|
||||
tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainId))
|
||||
tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainID))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1102,7 +1137,7 @@ func TestEIP155Transition(t *testing.T) {
|
||||
}
|
||||
|
||||
// generate an invalid chain id transaction
|
||||
config := ¶ms.ChainConfig{ChainId: big.NewInt(2), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)}
|
||||
config := ¶ms.ChainConfig{ChainID: big.NewInt(2), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)}
|
||||
blocks, _ = GenerateChain(config, blocks[len(blocks)-1], ethash.NewFaker(), db, 4, func(i int, block *BlockGen) {
|
||||
var (
|
||||
tx *types.Transaction
|
||||
@@ -1111,8 +1146,7 @@ func TestEIP155Transition(t *testing.T) {
|
||||
return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), 21000, new(big.Int), nil), signer, key)
|
||||
}
|
||||
)
|
||||
switch i {
|
||||
case 0:
|
||||
if i == 0 {
|
||||
tx, err = basicTx(types.NewEIP155Signer(big.NewInt(2)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1129,14 +1163,14 @@ func TestEIP155Transition(t *testing.T) {
|
||||
func TestEIP161AccountRemoval(t *testing.T) {
|
||||
// Configure and generate a sample block chain
|
||||
var (
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
funds = big.NewInt(1000000000)
|
||||
theAddr = common.Address{1}
|
||||
gspec = &Genesis{
|
||||
Config: ¶ms.ChainConfig{
|
||||
ChainId: big.NewInt(1),
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: new(big.Int),
|
||||
EIP155Block: new(big.Int),
|
||||
EIP158Block: big.NewInt(2),
|
||||
@@ -1152,7 +1186,7 @@ func TestEIP161AccountRemoval(t *testing.T) {
|
||||
var (
|
||||
tx *types.Transaction
|
||||
err error
|
||||
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
||||
signer = types.NewEIP155Signer(gspec.Config.ChainID)
|
||||
)
|
||||
switch i {
|
||||
case 0:
|
||||
@@ -1201,7 +1235,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) {
|
||||
// Generate a canonical chain to act as the main dataset
|
||||
engine := ethash.NewFaker()
|
||||
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
genesis := new(Genesis).MustCommit(db)
|
||||
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
|
||||
|
||||
@@ -1217,7 +1251,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) {
|
||||
}
|
||||
// Import the canonical and fork chain side by side, verifying the current block
|
||||
// and current header consistency
|
||||
diskdb, _ := ethdb.NewMemDatabase()
|
||||
diskdb := ethdb.NewMemDatabase()
|
||||
new(Genesis).MustCommit(diskdb)
|
||||
|
||||
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{})
|
||||
@@ -1246,7 +1280,7 @@ func TestTrieForkGC(t *testing.T) {
|
||||
// Generate a canonical chain to act as the main dataset
|
||||
engine := ethash.NewFaker()
|
||||
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
genesis := new(Genesis).MustCommit(db)
|
||||
blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*triesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
|
||||
|
||||
@@ -1261,7 +1295,7 @@ func TestTrieForkGC(t *testing.T) {
|
||||
forks[i] = fork[0]
|
||||
}
|
||||
// Import the canonical and fork chain side by side, forcing the trie cache to cache both
|
||||
diskdb, _ := ethdb.NewMemDatabase()
|
||||
diskdb := ethdb.NewMemDatabase()
|
||||
new(Genesis).MustCommit(diskdb)
|
||||
|
||||
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{})
|
||||
@@ -1278,8 +1312,8 @@ func TestTrieForkGC(t *testing.T) {
|
||||
}
|
||||
// Dereference all the recent tries and ensure no past trie is left in
|
||||
for i := 0; i < triesInMemory; i++ {
|
||||
chain.stateCache.TrieDB().Dereference(blocks[len(blocks)-1-i].Root(), common.Hash{})
|
||||
chain.stateCache.TrieDB().Dereference(forks[len(blocks)-1-i].Root(), common.Hash{})
|
||||
chain.stateCache.TrieDB().Dereference(blocks[len(blocks)-1-i].Root())
|
||||
chain.stateCache.TrieDB().Dereference(forks[len(blocks)-1-i].Root())
|
||||
}
|
||||
if len(chain.stateCache.TrieDB().Nodes()) > 0 {
|
||||
t.Fatalf("stale tries still alive after garbase collection")
|
||||
@@ -1292,7 +1326,7 @@ func TestLargeReorgTrieGC(t *testing.T) {
|
||||
// Generate the original common chain segment and the two competing forks
|
||||
engine := ethash.NewFaker()
|
||||
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
genesis := new(Genesis).MustCommit(db)
|
||||
|
||||
shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
|
||||
@@ -1300,7 +1334,7 @@ func TestLargeReorgTrieGC(t *testing.T) {
|
||||
competitor, _ := GenerateChain(params.TestChainConfig, shared[len(shared)-1], engine, db, 2*triesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) })
|
||||
|
||||
// Import the shared chain and the original canonical one
|
||||
diskdb, _ := ethdb.NewMemDatabase()
|
||||
diskdb := ethdb.NewMemDatabase()
|
||||
new(Genesis).MustCommit(diskdb)
|
||||
|
||||
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{})
|
||||
@@ -1338,3 +1372,114 @@ func TestLargeReorgTrieGC(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks large blocks with value transfers to non-existing accounts
|
||||
func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks int, recipientFn func(uint64) common.Address, dataFn func(uint64) []byte) {
|
||||
var (
|
||||
signer = types.HomesteadSigner{}
|
||||
testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey)
|
||||
bankFunds = big.NewInt(100000000000000000)
|
||||
gspec = Genesis{
|
||||
Config: params.TestChainConfig,
|
||||
Alloc: GenesisAlloc{
|
||||
testBankAddress: {Balance: bankFunds},
|
||||
common.HexToAddress("0xc0de"): {
|
||||
Code: []byte{0x60, 0x01, 0x50},
|
||||
Balance: big.NewInt(0),
|
||||
}, // push 1, pop
|
||||
},
|
||||
GasLimit: 100e6, // 100 M
|
||||
}
|
||||
)
|
||||
// Generate the original common chain segment and the two competing forks
|
||||
engine := ethash.NewFaker()
|
||||
db := ethdb.NewMemDatabase()
|
||||
genesis := gspec.MustCommit(db)
|
||||
|
||||
blockGenerator := func(i int, block *BlockGen) {
|
||||
block.SetCoinbase(common.Address{1})
|
||||
for txi := 0; txi < numTxs; txi++ {
|
||||
uniq := uint64(i*numTxs + txi)
|
||||
recipient := recipientFn(uniq)
|
||||
//recipient := common.BigToAddress(big.NewInt(0).SetUint64(1337 + uniq))
|
||||
tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testBankKey)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
block.AddTx(tx)
|
||||
}
|
||||
}
|
||||
|
||||
shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, numBlocks, blockGenerator)
|
||||
b.StopTimer()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Import the shared chain and the original canonical one
|
||||
diskdb := ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(diskdb)
|
||||
|
||||
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{})
|
||||
if err != nil {
|
||||
b.Fatalf("failed to create tester chain: %v", err)
|
||||
}
|
||||
b.StartTimer()
|
||||
if _, err := chain.InsertChain(shared); err != nil {
|
||||
b.Fatalf("failed to insert shared chain: %v", err)
|
||||
}
|
||||
b.StopTimer()
|
||||
if got := chain.CurrentBlock().Transactions().Len(); got != numTxs*numBlocks {
|
||||
b.Fatalf("Transactions were not included, expected %d, got %d", (numTxs * numBlocks), got)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
func BenchmarkBlockChain_1x1000ValueTransferToNonexisting(b *testing.B) {
|
||||
var (
|
||||
numTxs = 1000
|
||||
numBlocks = 1
|
||||
)
|
||||
|
||||
recipientFn := func(nonce uint64) common.Address {
|
||||
return common.BigToAddress(big.NewInt(0).SetUint64(1337 + nonce))
|
||||
}
|
||||
dataFn := func(nonce uint64) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn)
|
||||
}
|
||||
func BenchmarkBlockChain_1x1000ValueTransferToExisting(b *testing.B) {
|
||||
var (
|
||||
numTxs = 1000
|
||||
numBlocks = 1
|
||||
)
|
||||
b.StopTimer()
|
||||
b.ResetTimer()
|
||||
|
||||
recipientFn := func(nonce uint64) common.Address {
|
||||
return common.BigToAddress(big.NewInt(0).SetUint64(1337))
|
||||
}
|
||||
dataFn := func(nonce uint64) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn)
|
||||
}
|
||||
func BenchmarkBlockChain_1x1000Executions(b *testing.B) {
|
||||
var (
|
||||
numTxs = 1000
|
||||
numBlocks = 1
|
||||
)
|
||||
b.StopTimer()
|
||||
b.ResetTimer()
|
||||
|
||||
recipientFn := func(nonce uint64) common.Address {
|
||||
return common.BigToAddress(big.NewInt(0).SetUint64(0xc0de))
|
||||
}
|
||||
dataFn := func(nonce uint64) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn)
|
||||
}
|
||||
|
||||
+18
-12
@@ -22,16 +22,22 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// errSectionOutOfBounds is returned if the user tried to add more bloom filters
|
||||
// to the batch than available space, or if tries to retrieve above the capacity,
|
||||
var errSectionOutOfBounds = errors.New("section out of bounds")
|
||||
var (
|
||||
// errSectionOutOfBounds is returned if the user tried to add more bloom filters
|
||||
// to the batch than available space, or if tries to retrieve above the capacity.
|
||||
errSectionOutOfBounds = errors.New("section out of bounds")
|
||||
|
||||
// errBloomBitOutOfBounds is returned if the user tried to retrieve specified
|
||||
// bit bloom above the capacity.
|
||||
errBloomBitOutOfBounds = errors.New("bloom bit out of bounds")
|
||||
)
|
||||
|
||||
// Generator takes a number of bloom filters and generates the rotated bloom bits
|
||||
// to be used for batched filtering.
|
||||
type Generator struct {
|
||||
blooms [types.BloomBitLength][]byte // Rotated blooms for per-bit matching
|
||||
sections uint // Number of sections to batch together
|
||||
nextBit uint // Next bit to set when adding a bloom
|
||||
nextSec uint // Next section to set when adding a bloom
|
||||
}
|
||||
|
||||
// NewGenerator creates a rotated bloom generator that can iteratively fill a
|
||||
@@ -51,15 +57,15 @@ func NewGenerator(sections uint) (*Generator, error) {
|
||||
// in memory accordingly.
|
||||
func (b *Generator) AddBloom(index uint, bloom types.Bloom) error {
|
||||
// Make sure we're not adding more bloom filters than our capacity
|
||||
if b.nextBit >= b.sections {
|
||||
if b.nextSec >= b.sections {
|
||||
return errSectionOutOfBounds
|
||||
}
|
||||
if b.nextBit != index {
|
||||
if b.nextSec != index {
|
||||
return errors.New("bloom filter with unexpected index")
|
||||
}
|
||||
// Rotate the bloom and insert into our collection
|
||||
byteIndex := b.nextBit / 8
|
||||
bitMask := byte(1) << byte(7-b.nextBit%8)
|
||||
byteIndex := b.nextSec / 8
|
||||
bitMask := byte(1) << byte(7-b.nextSec%8)
|
||||
|
||||
for i := 0; i < types.BloomBitLength; i++ {
|
||||
bloomByteIndex := types.BloomByteLength - 1 - i/8
|
||||
@@ -69,7 +75,7 @@ func (b *Generator) AddBloom(index uint, bloom types.Bloom) error {
|
||||
b.blooms[i][byteIndex] |= bitMask
|
||||
}
|
||||
}
|
||||
b.nextBit++
|
||||
b.nextSec++
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -77,11 +83,11 @@ func (b *Generator) AddBloom(index uint, bloom types.Bloom) error {
|
||||
// Bitset returns the bit vector belonging to the given bit index after all
|
||||
// blooms have been added.
|
||||
func (b *Generator) Bitset(idx uint) ([]byte, error) {
|
||||
if b.nextBit != b.sections {
|
||||
if b.nextSec != b.sections {
|
||||
return nil, errors.New("bloom not fully generated yet")
|
||||
}
|
||||
if idx >= b.sections {
|
||||
return nil, errSectionOutOfBounds
|
||||
if idx >= types.BloomBitLength {
|
||||
return nil, errBloomBitOutOfBounds
|
||||
}
|
||||
return b.blooms[idx], nil
|
||||
}
|
||||
|
||||
+6
-6
@@ -59,7 +59,7 @@ type partialMatches struct {
|
||||
// It can also have the actual results set to be used as a delivery data struct.
|
||||
//
|
||||
// The contest and error fields are used by the light client to terminate matching
|
||||
// early if an error is enountered on some path of the pipeline.
|
||||
// early if an error is encountered on some path of the pipeline.
|
||||
type Retrieval struct {
|
||||
Bit uint
|
||||
Sections []uint64
|
||||
@@ -218,7 +218,7 @@ func (m *Matcher) Start(ctx context.Context, begin, end uint64, results chan uin
|
||||
// run creates a daisy-chain of sub-matchers, one for the address set and one
|
||||
// for each topic set, each sub-matcher receiving a section only if the previous
|
||||
// ones have all found a potential match in one of the blocks of the section,
|
||||
// then binary AND-ing its own matches and forwaring the result to the next one.
|
||||
// then binary AND-ing its own matches and forwarding the result to the next one.
|
||||
//
|
||||
// The method starts feeding the section indexes into the first sub-matcher on a
|
||||
// new goroutine and returns a sink channel receiving the results.
|
||||
@@ -392,7 +392,7 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) {
|
||||
shutdown = session.quit // Shutdown request channel, will gracefully wait for pending requests
|
||||
)
|
||||
|
||||
// assign is a helper method fo try to assign a pending bit an an actively
|
||||
// assign is a helper method fo try to assign a pending bit an actively
|
||||
// listening servicer, or schedule it up for later when one arrives.
|
||||
assign := func(bit uint) {
|
||||
select {
|
||||
@@ -543,7 +543,7 @@ func (s *MatcherSession) Error() error {
|
||||
}
|
||||
|
||||
// AllocateRetrieval assigns a bloom bit index to a client process that can either
|
||||
// immediately reuest and fetch the section contents assigned to this bit or wait
|
||||
// immediately request and fetch the section contents assigned to this bit or wait
|
||||
// a little while for more sections to be requested.
|
||||
func (s *MatcherSession) AllocateRetrieval() (uint, bool) {
|
||||
fetcher := make(chan uint)
|
||||
@@ -599,8 +599,8 @@ func (s *MatcherSession) DeliverSections(bit uint, sections []uint64, bitsets []
|
||||
}
|
||||
}
|
||||
|
||||
// Multiplex polls the matcher session for rerieval tasks and multiplexes it into
|
||||
// the reuested retrieval queue to be serviced together with other sessions.
|
||||
// Multiplex polls the matcher session for retrieval tasks and multiplexes it into
|
||||
// the requested retrieval queue to be serviced together with other sessions.
|
||||
//
|
||||
// This method will block for the lifetime of the session. Even after termination
|
||||
// of the session, any request in-flight need to be responded to! Empty responses
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ func testMatcher(t *testing.T, filter [][]bloomIndexes, start, blocks uint64, in
|
||||
// Track the number of retrieval requests made
|
||||
var requested uint32
|
||||
|
||||
// Start the matching session for the filter and the retriver goroutines
|
||||
// Start the matching session for the filter and the retriever goroutines
|
||||
quit := make(chan struct{})
|
||||
matches := make(chan uint64, 16)
|
||||
|
||||
|
||||
+71
-23
@@ -17,6 +17,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
@@ -36,11 +38,11 @@ import (
|
||||
type ChainIndexerBackend interface {
|
||||
// Reset initiates the processing of a new chain segment, potentially terminating
|
||||
// any partially completed operations (in case of a reorg).
|
||||
Reset(section uint64, prevHead common.Hash) error
|
||||
Reset(ctx context.Context, section uint64, prevHead common.Hash) error
|
||||
|
||||
// Process crunches through the next header in the chain segment. The caller
|
||||
// will ensure a sequential order of headers.
|
||||
Process(header *types.Header)
|
||||
Process(ctx context.Context, header *types.Header) error
|
||||
|
||||
// Commit finalizes the section metadata and stores it into the database.
|
||||
Commit() error
|
||||
@@ -70,9 +72,11 @@ type ChainIndexer struct {
|
||||
backend ChainIndexerBackend // Background processor generating the index data content
|
||||
children []*ChainIndexer // Child indexers to cascade chain updates to
|
||||
|
||||
active uint32 // Flag whether the event loop was started
|
||||
update chan struct{} // Notification channel that headers should be processed
|
||||
quit chan chan error // Quit channel to tear down running goroutines
|
||||
active uint32 // Flag whether the event loop was started
|
||||
update chan struct{} // Notification channel that headers should be processed
|
||||
quit chan chan error // Quit channel to tear down running goroutines
|
||||
ctx context.Context
|
||||
ctxCancel func()
|
||||
|
||||
sectionSize uint64 // Number of blocks in a single chain segment to process
|
||||
confirmsReq uint64 // Number of confirmations before processing a completed segment
|
||||
@@ -81,6 +85,9 @@ type ChainIndexer struct {
|
||||
knownSections uint64 // Number of sections known to be complete (block wise)
|
||||
cascadedHead uint64 // Block number of the last completed section cascaded to subindexers
|
||||
|
||||
checkpointSections uint64 // Number of sections covered by the checkpoint
|
||||
checkpointHead common.Hash // Section head belonging to the checkpoint
|
||||
|
||||
throttling time.Duration // Disk throttling to prevent a heavy upgrade from hogging resources
|
||||
|
||||
log log.Logger
|
||||
@@ -104,17 +111,26 @@ func NewChainIndexer(chainDb, indexDb ethdb.Database, backend ChainIndexerBacken
|
||||
}
|
||||
// Initialize database dependent fields and start the updater
|
||||
c.loadValidSections()
|
||||
c.ctx, c.ctxCancel = context.WithCancel(context.Background())
|
||||
|
||||
go c.updateLoop()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// AddKnownSectionHead marks a new section head as known/processed if it is newer
|
||||
// than the already known best section head
|
||||
func (c *ChainIndexer) AddKnownSectionHead(section uint64, shead common.Hash) {
|
||||
// AddCheckpoint adds a checkpoint. Sections are never processed and the chain
|
||||
// is not expected to be available before this point. The indexer assumes that
|
||||
// the backend has sufficient information available to process subsequent sections.
|
||||
//
|
||||
// Note: knownSections == 0 and storedSections == checkpointSections until
|
||||
// syncing reaches the checkpoint
|
||||
func (c *ChainIndexer) AddCheckpoint(section uint64, shead common.Hash) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.checkpointSections = section + 1
|
||||
c.checkpointHead = shead
|
||||
|
||||
if section < c.storedSections {
|
||||
return
|
||||
}
|
||||
@@ -137,6 +153,8 @@ func (c *ChainIndexer) Start(chain ChainIndexerChain) {
|
||||
func (c *ChainIndexer) Close() error {
|
||||
var errs []error
|
||||
|
||||
c.ctxCancel()
|
||||
|
||||
// Tear down the primary update loop
|
||||
errc := make(chan error)
|
||||
c.quit <- errc
|
||||
@@ -206,7 +224,7 @@ func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainE
|
||||
|
||||
// TODO(karalabe): This operation is expensive and might block, causing the event system to
|
||||
// potentially also lock up. We need to do with on a different thread somehow.
|
||||
if h := FindCommonAncestor(c.chainDb, prevHeader, header); h != nil {
|
||||
if h := rawdb.FindCommonAncestor(c.chainDb, prevHeader, header); h != nil {
|
||||
c.newHead(h.Number.Uint64(), true)
|
||||
}
|
||||
}
|
||||
@@ -225,16 +243,23 @@ func (c *ChainIndexer) newHead(head uint64, reorg bool) {
|
||||
// If a reorg happened, invalidate all sections until that point
|
||||
if reorg {
|
||||
// Revert the known section number to the reorg point
|
||||
changed := head / c.sectionSize
|
||||
if changed < c.knownSections {
|
||||
c.knownSections = changed
|
||||
known := head / c.sectionSize
|
||||
stored := known
|
||||
if known < c.checkpointSections {
|
||||
known = 0
|
||||
}
|
||||
if stored < c.checkpointSections {
|
||||
stored = c.checkpointSections
|
||||
}
|
||||
if known < c.knownSections {
|
||||
c.knownSections = known
|
||||
}
|
||||
// Revert the stored sections from the database to the reorg point
|
||||
if changed < c.storedSections {
|
||||
c.setValidSections(changed)
|
||||
if stored < c.storedSections {
|
||||
c.setValidSections(stored)
|
||||
}
|
||||
// Update the new head number to the finalized section end and notify children
|
||||
head = changed * c.sectionSize
|
||||
head = known * c.sectionSize
|
||||
|
||||
if head < c.cascadedHead {
|
||||
c.cascadedHead = head
|
||||
@@ -248,7 +273,18 @@ func (c *ChainIndexer) newHead(head uint64, reorg bool) {
|
||||
var sections uint64
|
||||
if head >= c.confirmsReq {
|
||||
sections = (head + 1 - c.confirmsReq) / c.sectionSize
|
||||
if sections < c.checkpointSections {
|
||||
sections = 0
|
||||
}
|
||||
if sections > c.knownSections {
|
||||
if c.knownSections < c.checkpointSections {
|
||||
// syncing reached the checkpoint, verify section head
|
||||
syncedHead := rawdb.ReadCanonicalHash(c.chainDb, c.checkpointSections*c.sectionSize-1)
|
||||
if syncedHead != c.checkpointHead {
|
||||
c.log.Error("Synced chain does not match checkpoint", "number", c.checkpointSections*c.sectionSize-1, "expected", c.checkpointHead, "synced", syncedHead)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.knownSections = sections
|
||||
|
||||
select {
|
||||
@@ -296,6 +332,12 @@ func (c *ChainIndexer) updateLoop() {
|
||||
c.lock.Unlock()
|
||||
newHead, err := c.processSection(section, oldHead)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
<-c.quit <- nil
|
||||
return
|
||||
default:
|
||||
}
|
||||
c.log.Error("Section processing failed", "error", err)
|
||||
}
|
||||
c.lock.Lock()
|
||||
@@ -308,7 +350,6 @@ func (c *ChainIndexer) updateLoop() {
|
||||
updating = false
|
||||
c.log.Info("Finished upgrading chain index")
|
||||
}
|
||||
|
||||
c.cascadedHead = c.storedSections*c.sectionSize - 1
|
||||
for _, child := range c.children {
|
||||
c.log.Trace("Cascading chain index update", "head", c.cascadedHead)
|
||||
@@ -343,27 +384,28 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
|
||||
|
||||
// Reset and partial processing
|
||||
|
||||
if err := c.backend.Reset(section, lastHead); err != nil {
|
||||
if err := c.backend.Reset(c.ctx, section, lastHead); err != nil {
|
||||
c.setValidSections(0)
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
for number := section * c.sectionSize; number < (section+1)*c.sectionSize; number++ {
|
||||
hash := GetCanonicalHash(c.chainDb, number)
|
||||
hash := rawdb.ReadCanonicalHash(c.chainDb, number)
|
||||
if hash == (common.Hash{}) {
|
||||
return common.Hash{}, fmt.Errorf("canonical block #%d unknown", number)
|
||||
}
|
||||
header := GetHeader(c.chainDb, hash, number)
|
||||
header := rawdb.ReadHeader(c.chainDb, hash, number)
|
||||
if header == nil {
|
||||
return common.Hash{}, fmt.Errorf("block #%d [%x…] not found", number, hash[:4])
|
||||
} else if header.ParentHash != lastHead {
|
||||
return common.Hash{}, fmt.Errorf("chain reorged during section processing")
|
||||
}
|
||||
c.backend.Process(header)
|
||||
if err := c.backend.Process(c.ctx, header); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
lastHead = header.Hash()
|
||||
}
|
||||
if err := c.backend.Commit(); err != nil {
|
||||
c.log.Error("Section commit failed", "error", err)
|
||||
return common.Hash{}, err
|
||||
}
|
||||
return lastHead, nil
|
||||
@@ -387,8 +429,14 @@ func (c *ChainIndexer) AddChildIndexer(indexer *ChainIndexer) {
|
||||
c.children = append(c.children, indexer)
|
||||
|
||||
// Cascade any pending updates to new children too
|
||||
if c.storedSections > 0 {
|
||||
indexer.newHead(c.storedSections*c.sectionSize-1, false)
|
||||
sections := c.storedSections
|
||||
if c.knownSections < sections {
|
||||
// if a section is "stored" but not "known" then it is a checkpoint without
|
||||
// available chain data so we should not cascade it yet
|
||||
sections = c.knownSections
|
||||
}
|
||||
if sections > 0 {
|
||||
indexer.newHead(sections*c.sectionSize-1, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-6
@@ -17,6 +17,7 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
@@ -47,7 +49,7 @@ func TestChainIndexerWithChildren(t *testing.T) {
|
||||
// multiple backends. The section size and required confirmation count parameters
|
||||
// are randomized.
|
||||
func testChainIndexer(t *testing.T, count int) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
defer db.Close()
|
||||
|
||||
// Create a chain of indexers and ensure they all report empty
|
||||
@@ -92,10 +94,10 @@ func testChainIndexer(t *testing.T, count int) {
|
||||
inject := func(number uint64) {
|
||||
header := &types.Header{Number: big.NewInt(int64(number)), Extra: big.NewInt(rand.Int63()).Bytes()}
|
||||
if number > 0 {
|
||||
header.ParentHash = GetCanonicalHash(db, number-1)
|
||||
header.ParentHash = rawdb.ReadCanonicalHash(db, number-1)
|
||||
}
|
||||
WriteHeader(db, header)
|
||||
WriteCanonicalHash(db, header.Hash(), number)
|
||||
rawdb.WriteHeader(db, header)
|
||||
rawdb.WriteCanonicalHash(db, header.Hash(), number)
|
||||
}
|
||||
// Start indexer with an already existing chain
|
||||
for i := uint64(0); i <= 100; i++ {
|
||||
@@ -209,13 +211,13 @@ func (b *testChainIndexBackend) reorg(headNum uint64) uint64 {
|
||||
return b.stored * b.indexer.sectionSize
|
||||
}
|
||||
|
||||
func (b *testChainIndexBackend) Reset(section uint64, prevHead common.Hash) error {
|
||||
func (b *testChainIndexBackend) Reset(ctx context.Context, section uint64, prevHead common.Hash) error {
|
||||
b.section = section
|
||||
b.headerCnt = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *testChainIndexBackend) Process(header *types.Header) {
|
||||
func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Header) error {
|
||||
b.headerCnt++
|
||||
if b.headerCnt > b.indexer.sectionSize {
|
||||
b.t.Error("Processing too many headers")
|
||||
@@ -226,6 +228,7 @@ func (b *testChainIndexBackend) Process(header *types.Header) {
|
||||
b.t.Fatal("Unexpected call to Process")
|
||||
case b.processCh <- header.Number.Uint64():
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *testChainIndexBackend) Commit() error {
|
||||
|
||||
+14
-34
@@ -30,12 +30,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// So we can deterministically seed different blockchains
|
||||
var (
|
||||
canonicalSeed = 1
|
||||
forkSeed = 2
|
||||
)
|
||||
|
||||
// BlockGen creates blocks for testing.
|
||||
// See GenerateChain for a detailed explanation.
|
||||
type BlockGen struct {
|
||||
@@ -82,11 +76,23 @@ func (b *BlockGen) SetExtra(data []byte) {
|
||||
// added. Notably, contract code relying on the BLOCKHASH instruction
|
||||
// will panic during execution.
|
||||
func (b *BlockGen) AddTx(tx *types.Transaction) {
|
||||
b.AddTxWithChain(nil, tx)
|
||||
}
|
||||
|
||||
// AddTxWithChain adds a transaction to the generated block. If no coinbase has
|
||||
// been set, the block's coinbase is set to the zero address.
|
||||
//
|
||||
// AddTxWithChain panics if the transaction cannot be executed. In addition to
|
||||
// the protocol-imposed limitations (gas limit, etc.), there are some
|
||||
// further limitations on the content of transactions that can be
|
||||
// added. If contract code relies on the BLOCKHASH instruction,
|
||||
// the block in chain will be returned.
|
||||
func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
|
||||
if b.gasPool == nil {
|
||||
b.SetCoinbase(common.Address{})
|
||||
}
|
||||
b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs))
|
||||
receipt, _, err := ApplyTransaction(b.config, nil, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
|
||||
receipt, _, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -234,38 +240,12 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
|
||||
Difficulty: parent.Difficulty(),
|
||||
UncleHash: parent.UncleHash(),
|
||||
}),
|
||||
GasLimit: CalcGasLimit(parent),
|
||||
GasLimit: CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()),
|
||||
Number: new(big.Int).Add(parent.Number(), common.Big1),
|
||||
Time: time,
|
||||
}
|
||||
}
|
||||
|
||||
// newCanonical creates a chain database, and injects a deterministic canonical
|
||||
// chain. Depending on the full flag, if creates either a full block chain or a
|
||||
// header only chain.
|
||||
func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *BlockChain, error) {
|
||||
// Initialize a fresh chain with only a genesis block
|
||||
gspec := new(Genesis)
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
genesis := gspec.MustCommit(db)
|
||||
|
||||
blockchain, _ := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{})
|
||||
// Create and inject the requested chain
|
||||
if n == 0 {
|
||||
return db, blockchain, nil
|
||||
}
|
||||
if full {
|
||||
// Full block-chain requested
|
||||
blocks := makeBlockChain(genesis, n, engine, db, canonicalSeed)
|
||||
_, err := blockchain.InsertChain(blocks)
|
||||
return db, blockchain, err
|
||||
}
|
||||
// Header-only chain requested
|
||||
headers := makeHeaderChain(genesis.Header(), n, engine, db, canonicalSeed)
|
||||
_, err := blockchain.InsertHeaderChain(headers, 1)
|
||||
return db, blockchain, err
|
||||
}
|
||||
|
||||
// makeHeaderChain creates a deterministic chain of headers rooted at parent.
|
||||
func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header {
|
||||
blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed)
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ func ExampleGenerateChain() {
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
)
|
||||
|
||||
// Ensure that key1 has some funds in the genesis block.
|
||||
|
||||
+7
-7
@@ -32,13 +32,13 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
forkBlock := big.NewInt(32)
|
||||
|
||||
// Generate a common prefix for both pro-forkers and non-forkers
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
gspec := new(Genesis)
|
||||
genesis := gspec.MustCommit(db)
|
||||
prefix, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {})
|
||||
|
||||
// Create the concurrent, conflicting two nodes
|
||||
proDb, _ := ethdb.NewMemDatabase()
|
||||
proDb := ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(proDb)
|
||||
|
||||
proConf := *params.TestChainConfig
|
||||
@@ -48,7 +48,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
proBc, _ := NewBlockChain(proDb, nil, &proConf, ethash.NewFaker(), vm.Config{})
|
||||
defer proBc.Stop()
|
||||
|
||||
conDb, _ := ethdb.NewMemDatabase()
|
||||
conDb := ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(conDb)
|
||||
|
||||
conConf := *params.TestChainConfig
|
||||
@@ -67,7 +67,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
// Try to expand both pro-fork and non-fork chains iteratively with other camp's blocks
|
||||
for i := int64(0); i < params.DAOForkExtraRange.Int64(); i++ {
|
||||
// Create a pro-fork block, and try to feed into the no-fork chain
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(db)
|
||||
bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{})
|
||||
defer bc.Stop()
|
||||
@@ -92,7 +92,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err)
|
||||
}
|
||||
// Create a no-fork block, and try to feed into the pro-fork chain
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(db)
|
||||
bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{})
|
||||
defer bc.Stop()
|
||||
@@ -118,7 +118,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// Verify that contra-forkers accept pro-fork extra-datas after forking finishes
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(db)
|
||||
bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{})
|
||||
defer bc.Stop()
|
||||
@@ -138,7 +138,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||
t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err)
|
||||
}
|
||||
// Verify that pro-forkers accept contra-fork extra-datas after forking finishes
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
gspec.MustCommit(db)
|
||||
bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{})
|
||||
defer bc.Stop()
|
||||
|
||||
-652
@@ -1,652 +0,0 @@
|
||||
// Copyright 2015 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 (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// DatabaseReader wraps the Get method of a backing data store.
|
||||
type DatabaseReader interface {
|
||||
Get(key []byte) (value []byte, err error)
|
||||
}
|
||||
|
||||
// DatabaseDeleter wraps the Delete method of a backing data store.
|
||||
type DatabaseDeleter interface {
|
||||
Delete(key []byte) error
|
||||
}
|
||||
|
||||
var (
|
||||
headHeaderKey = []byte("LastHeader")
|
||||
headBlockKey = []byte("LastBlock")
|
||||
headFastKey = []byte("LastFast")
|
||||
trieSyncKey = []byte("TrieSync")
|
||||
|
||||
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`).
|
||||
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
|
||||
tdSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + tdSuffix -> td
|
||||
numSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + numSuffix -> hash
|
||||
blockHashPrefix = []byte("H") // blockHashPrefix + hash -> num (uint64 big endian)
|
||||
bodyPrefix = []byte("b") // bodyPrefix + num (uint64 big endian) + hash -> block body
|
||||
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
|
||||
lookupPrefix = []byte("l") // lookupPrefix + hash -> transaction/receipt lookup metadata
|
||||
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
|
||||
|
||||
preimagePrefix = "secure-key-" // preimagePrefix + hash -> preimage
|
||||
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
||||
|
||||
// Chain index prefixes (use `i` + single byte to avoid mixing data types).
|
||||
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
|
||||
|
||||
// used by old db, now only used for conversion
|
||||
oldReceiptsPrefix = []byte("receipts-")
|
||||
oldTxMetaSuffix = []byte{0x01}
|
||||
|
||||
ErrChainConfigNotFound = errors.New("ChainConfig not found") // general config not found error
|
||||
|
||||
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
|
||||
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
||||
)
|
||||
|
||||
// TxLookupEntry is a positional metadata to help looking up the data content of
|
||||
// a transaction or receipt given only its hash.
|
||||
type TxLookupEntry struct {
|
||||
BlockHash common.Hash
|
||||
BlockIndex uint64
|
||||
Index uint64
|
||||
}
|
||||
|
||||
// encodeBlockNumber encodes a block number as big endian uint64
|
||||
func encodeBlockNumber(number uint64) []byte {
|
||||
enc := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(enc, number)
|
||||
return enc
|
||||
}
|
||||
|
||||
// GetCanonicalHash retrieves a hash assigned to a canonical block number.
|
||||
func GetCanonicalHash(db DatabaseReader, number uint64) common.Hash {
|
||||
data, _ := db.Get(append(append(headerPrefix, encodeBlockNumber(number)...), numSuffix...))
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return common.BytesToHash(data)
|
||||
}
|
||||
|
||||
// missingNumber is returned by GetBlockNumber if no header with the
|
||||
// given block hash has been stored in the database
|
||||
const missingNumber = uint64(0xffffffffffffffff)
|
||||
|
||||
// GetBlockNumber returns the block number assigned to a block hash
|
||||
// if the corresponding header is present in the database
|
||||
func GetBlockNumber(db DatabaseReader, hash common.Hash) uint64 {
|
||||
data, _ := db.Get(append(blockHashPrefix, hash.Bytes()...))
|
||||
if len(data) != 8 {
|
||||
return missingNumber
|
||||
}
|
||||
return binary.BigEndian.Uint64(data)
|
||||
}
|
||||
|
||||
// GetHeadHeaderHash retrieves the hash of the current canonical head block's
|
||||
// header. The difference between this and GetHeadBlockHash is that whereas the
|
||||
// last block hash is only updated upon a full block import, the last header
|
||||
// hash is updated already at header import, allowing head tracking for the
|
||||
// light synchronization mechanism.
|
||||
func GetHeadHeaderHash(db DatabaseReader) common.Hash {
|
||||
data, _ := db.Get(headHeaderKey)
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return common.BytesToHash(data)
|
||||
}
|
||||
|
||||
// GetHeadBlockHash retrieves the hash of the current canonical head block.
|
||||
func GetHeadBlockHash(db DatabaseReader) common.Hash {
|
||||
data, _ := db.Get(headBlockKey)
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return common.BytesToHash(data)
|
||||
}
|
||||
|
||||
// GetHeadFastBlockHash retrieves the hash of the current canonical head block during
|
||||
// fast synchronization. The difference between this and GetHeadBlockHash is that
|
||||
// whereas the last block hash is only updated upon a full block import, the last
|
||||
// fast hash is updated when importing pre-processed blocks.
|
||||
func GetHeadFastBlockHash(db DatabaseReader) common.Hash {
|
||||
data, _ := db.Get(headFastKey)
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return common.BytesToHash(data)
|
||||
}
|
||||
|
||||
// GetTrieSyncProgress retrieves the number of tries nodes fast synced to allow
|
||||
// reportinc correct numbers across restarts.
|
||||
func GetTrieSyncProgress(db DatabaseReader) uint64 {
|
||||
data, _ := db.Get(trieSyncKey)
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
}
|
||||
return new(big.Int).SetBytes(data).Uint64()
|
||||
}
|
||||
|
||||
// GetHeaderRLP retrieves a block header in its raw RLP database encoding, or nil
|
||||
// if the header's not found.
|
||||
func GetHeaderRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue {
|
||||
data, _ := db.Get(headerKey(hash, number))
|
||||
return data
|
||||
}
|
||||
|
||||
// GetHeader retrieves the block header corresponding to the hash, nil if none
|
||||
// found.
|
||||
func GetHeader(db DatabaseReader, hash common.Hash, number uint64) *types.Header {
|
||||
data := GetHeaderRLP(db, hash, number)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
header := new(types.Header)
|
||||
if err := rlp.Decode(bytes.NewReader(data), header); err != nil {
|
||||
log.Error("Invalid block header RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
// GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding.
|
||||
func GetBodyRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue {
|
||||
data, _ := db.Get(blockBodyKey(hash, number))
|
||||
return data
|
||||
}
|
||||
|
||||
func headerKey(hash common.Hash, number uint64) []byte {
|
||||
return append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
||||
}
|
||||
|
||||
func blockBodyKey(hash common.Hash, number uint64) []byte {
|
||||
return append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
||||
}
|
||||
|
||||
// GetBody retrieves the block body (transactons, uncles) corresponding to the
|
||||
// hash, nil if none found.
|
||||
func GetBody(db DatabaseReader, hash common.Hash, number uint64) *types.Body {
|
||||
data := GetBodyRLP(db, hash, number)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
body := new(types.Body)
|
||||
if err := rlp.Decode(bytes.NewReader(data), body); err != nil {
|
||||
log.Error("Invalid block body RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
// GetTd retrieves a block's total difficulty corresponding to the hash, nil if
|
||||
// none found.
|
||||
func GetTd(db DatabaseReader, hash common.Hash, number uint64) *big.Int {
|
||||
data, _ := db.Get(append(append(append(headerPrefix, encodeBlockNumber(number)...), hash[:]...), tdSuffix...))
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
td := new(big.Int)
|
||||
if err := rlp.Decode(bytes.NewReader(data), td); err != nil {
|
||||
log.Error("Invalid block total difficulty RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
return td
|
||||
}
|
||||
|
||||
// GetBlock retrieves an entire block corresponding to the hash, assembling it
|
||||
// back from the stored header and body. If either the header or body could not
|
||||
// be retrieved nil is returned.
|
||||
//
|
||||
// Note, due to concurrent download of header and block body the header and thus
|
||||
// canonical hash can be stored in the database but the body data not (yet).
|
||||
func GetBlock(db DatabaseReader, hash common.Hash, number uint64) *types.Block {
|
||||
// Retrieve the block header and body contents
|
||||
header := GetHeader(db, hash, number)
|
||||
if header == nil {
|
||||
return nil
|
||||
}
|
||||
body := GetBody(db, hash, number)
|
||||
if body == nil {
|
||||
return nil
|
||||
}
|
||||
// Reassemble the block and return
|
||||
return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles)
|
||||
}
|
||||
|
||||
// GetBlockReceipts retrieves the receipts generated by the transactions included
|
||||
// in a block given by its hash.
|
||||
func GetBlockReceipts(db DatabaseReader, hash common.Hash, number uint64) types.Receipts {
|
||||
data, _ := db.Get(append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash[:]...))
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
storageReceipts := []*types.ReceiptForStorage{}
|
||||
if err := rlp.DecodeBytes(data, &storageReceipts); err != nil {
|
||||
log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
receipts := make(types.Receipts, len(storageReceipts))
|
||||
for i, receipt := range storageReceipts {
|
||||
receipts[i] = (*types.Receipt)(receipt)
|
||||
}
|
||||
return receipts
|
||||
}
|
||||
|
||||
// GetTxLookupEntry retrieves the positional metadata associated with a transaction
|
||||
// hash to allow retrieving the transaction or receipt by hash.
|
||||
func GetTxLookupEntry(db DatabaseReader, hash common.Hash) (common.Hash, uint64, uint64) {
|
||||
// Load the positional metadata from disk and bail if it fails
|
||||
data, _ := db.Get(append(lookupPrefix, hash.Bytes()...))
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}, 0, 0
|
||||
}
|
||||
// Parse and return the contents of the lookup entry
|
||||
var entry TxLookupEntry
|
||||
if err := rlp.DecodeBytes(data, &entry); err != nil {
|
||||
log.Error("Invalid lookup entry RLP", "hash", hash, "err", err)
|
||||
return common.Hash{}, 0, 0
|
||||
}
|
||||
return entry.BlockHash, entry.BlockIndex, entry.Index
|
||||
}
|
||||
|
||||
// GetTransaction retrieves a specific transaction from the database, along with
|
||||
// its added positional metadata.
|
||||
func GetTransaction(db DatabaseReader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) {
|
||||
// Retrieve the lookup metadata and resolve the transaction from the body
|
||||
blockHash, blockNumber, txIndex := GetTxLookupEntry(db, hash)
|
||||
|
||||
if blockHash != (common.Hash{}) {
|
||||
body := GetBody(db, blockHash, blockNumber)
|
||||
if body == nil || len(body.Transactions) <= int(txIndex) {
|
||||
log.Error("Transaction referenced missing", "number", blockNumber, "hash", blockHash, "index", txIndex)
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
return body.Transactions[txIndex], blockHash, blockNumber, txIndex
|
||||
}
|
||||
// Old transaction representation, load the transaction and it's metadata separately
|
||||
data, _ := db.Get(hash.Bytes())
|
||||
if len(data) == 0 {
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
var tx types.Transaction
|
||||
if err := rlp.DecodeBytes(data, &tx); err != nil {
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
// Retrieve the blockchain positional metadata
|
||||
data, _ = db.Get(append(hash.Bytes(), oldTxMetaSuffix...))
|
||||
if len(data) == 0 {
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
var entry TxLookupEntry
|
||||
if err := rlp.DecodeBytes(data, &entry); err != nil {
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
return &tx, entry.BlockHash, entry.BlockIndex, entry.Index
|
||||
}
|
||||
|
||||
// GetReceipt retrieves a specific transaction receipt from the database, along with
|
||||
// its added positional metadata.
|
||||
func GetReceipt(db DatabaseReader, hash common.Hash) (*types.Receipt, common.Hash, uint64, uint64) {
|
||||
// Retrieve the lookup metadata and resolve the receipt from the receipts
|
||||
blockHash, blockNumber, receiptIndex := GetTxLookupEntry(db, hash)
|
||||
|
||||
if blockHash != (common.Hash{}) {
|
||||
receipts := GetBlockReceipts(db, blockHash, blockNumber)
|
||||
if len(receipts) <= int(receiptIndex) {
|
||||
log.Error("Receipt refereced missing", "number", blockNumber, "hash", blockHash, "index", receiptIndex)
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
return receipts[receiptIndex], blockHash, blockNumber, receiptIndex
|
||||
}
|
||||
// Old receipt representation, load the receipt and set an unknown metadata
|
||||
data, _ := db.Get(append(oldReceiptsPrefix, hash[:]...))
|
||||
if len(data) == 0 {
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
var receipt types.ReceiptForStorage
|
||||
err := rlp.DecodeBytes(data, &receipt)
|
||||
if err != nil {
|
||||
log.Error("Invalid receipt RLP", "hash", hash, "err", err)
|
||||
}
|
||||
return (*types.Receipt)(&receipt), common.Hash{}, 0, 0
|
||||
}
|
||||
|
||||
// GetBloomBits retrieves the compressed bloom bit vector belonging to the given
|
||||
// section and bit index from the.
|
||||
func GetBloomBits(db DatabaseReader, bit uint, section uint64, head common.Hash) ([]byte, error) {
|
||||
key := append(append(bloomBitsPrefix, make([]byte, 10)...), head.Bytes()...)
|
||||
|
||||
binary.BigEndian.PutUint16(key[1:], uint16(bit))
|
||||
binary.BigEndian.PutUint64(key[3:], section)
|
||||
|
||||
return db.Get(key)
|
||||
}
|
||||
|
||||
// WriteCanonicalHash stores the canonical hash for the given block number.
|
||||
func WriteCanonicalHash(db ethdb.Putter, hash common.Hash, number uint64) error {
|
||||
key := append(append(headerPrefix, encodeBlockNumber(number)...), numSuffix...)
|
||||
if err := db.Put(key, hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to store number to hash mapping", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHeadHeaderHash stores the head header's hash.
|
||||
func WriteHeadHeaderHash(db ethdb.Putter, hash common.Hash) error {
|
||||
if err := db.Put(headHeaderKey, hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to store last header's hash", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHeadBlockHash stores the head block's hash.
|
||||
func WriteHeadBlockHash(db ethdb.Putter, hash common.Hash) error {
|
||||
if err := db.Put(headBlockKey, hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to store last block's hash", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHeadFastBlockHash stores the fast head block's hash.
|
||||
func WriteHeadFastBlockHash(db ethdb.Putter, hash common.Hash) error {
|
||||
if err := db.Put(headFastKey, hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to store last fast block's hash", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteTrieSyncProgress stores the fast sync trie process counter to support
|
||||
// retrieving it across restarts.
|
||||
func WriteTrieSyncProgress(db ethdb.Putter, count uint64) error {
|
||||
if err := db.Put(trieSyncKey, new(big.Int).SetUint64(count).Bytes()); err != nil {
|
||||
log.Crit("Failed to store fast sync trie progress", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHeader serializes a block header into the database.
|
||||
func WriteHeader(db ethdb.Putter, header *types.Header) error {
|
||||
data, err := rlp.EncodeToBytes(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash := header.Hash().Bytes()
|
||||
num := header.Number.Uint64()
|
||||
encNum := encodeBlockNumber(num)
|
||||
key := append(blockHashPrefix, hash...)
|
||||
if err := db.Put(key, encNum); err != nil {
|
||||
log.Crit("Failed to store hash to number mapping", "err", err)
|
||||
}
|
||||
key = append(append(headerPrefix, encNum...), hash...)
|
||||
if err := db.Put(key, data); err != nil {
|
||||
log.Crit("Failed to store header", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteBody serializes the body of a block into the database.
|
||||
func WriteBody(db ethdb.Putter, hash common.Hash, number uint64, body *types.Body) error {
|
||||
data, err := rlp.EncodeToBytes(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return WriteBodyRLP(db, hash, number, data)
|
||||
}
|
||||
|
||||
// WriteBodyRLP writes a serialized body of a block into the database.
|
||||
func WriteBodyRLP(db ethdb.Putter, hash common.Hash, number uint64, rlp rlp.RawValue) error {
|
||||
key := append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
||||
if err := db.Put(key, rlp); err != nil {
|
||||
log.Crit("Failed to store block body", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteTd serializes the total difficulty of a block into the database.
|
||||
func WriteTd(db ethdb.Putter, hash common.Hash, number uint64, td *big.Int) error {
|
||||
data, err := rlp.EncodeToBytes(td)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := append(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...), tdSuffix...)
|
||||
if err := db.Put(key, data); err != nil {
|
||||
log.Crit("Failed to store block total difficulty", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteBlock serializes a block into the database, header and body separately.
|
||||
func WriteBlock(db ethdb.Putter, block *types.Block) error {
|
||||
// Store the body first to retain database consistency
|
||||
if err := WriteBody(db, block.Hash(), block.NumberU64(), block.Body()); err != nil {
|
||||
return err
|
||||
}
|
||||
// Store the header too, signaling full block ownership
|
||||
if err := WriteHeader(db, block.Header()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteBlockReceipts stores all the transaction receipts belonging to a block
|
||||
// as a single receipt slice. This is used during chain reorganisations for
|
||||
// rescheduling dropped transactions.
|
||||
func WriteBlockReceipts(db ethdb.Putter, hash common.Hash, number uint64, receipts types.Receipts) error {
|
||||
// Convert the receipts into their storage form and serialize them
|
||||
storageReceipts := make([]*types.ReceiptForStorage, len(receipts))
|
||||
for i, receipt := range receipts {
|
||||
storageReceipts[i] = (*types.ReceiptForStorage)(receipt)
|
||||
}
|
||||
bytes, err := rlp.EncodeToBytes(storageReceipts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Store the flattened receipt slice
|
||||
key := append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
||||
if err := db.Put(key, bytes); err != nil {
|
||||
log.Crit("Failed to store block receipts", "err", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteTxLookupEntries stores a positional metadata for every transaction from
|
||||
// a block, enabling hash based transaction and receipt lookups.
|
||||
func WriteTxLookupEntries(db ethdb.Putter, block *types.Block) error {
|
||||
// Iterate over each transaction and encode its metadata
|
||||
for i, tx := range block.Transactions() {
|
||||
entry := TxLookupEntry{
|
||||
BlockHash: block.Hash(),
|
||||
BlockIndex: block.NumberU64(),
|
||||
Index: uint64(i),
|
||||
}
|
||||
data, err := rlp.EncodeToBytes(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.Put(append(lookupPrefix, tx.Hash().Bytes()...), data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteBloomBits writes the compressed bloom bits vector belonging to the given
|
||||
// section and bit index.
|
||||
func WriteBloomBits(db ethdb.Putter, bit uint, section uint64, head common.Hash, bits []byte) {
|
||||
key := append(append(bloomBitsPrefix, make([]byte, 10)...), head.Bytes()...)
|
||||
|
||||
binary.BigEndian.PutUint16(key[1:], uint16(bit))
|
||||
binary.BigEndian.PutUint64(key[3:], section)
|
||||
|
||||
if err := db.Put(key, bits); err != nil {
|
||||
log.Crit("Failed to store bloom bits", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteCanonicalHash removes the number to hash canonical mapping.
|
||||
func DeleteCanonicalHash(db DatabaseDeleter, number uint64) {
|
||||
db.Delete(append(append(headerPrefix, encodeBlockNumber(number)...), numSuffix...))
|
||||
}
|
||||
|
||||
// DeleteHeader removes all block header data associated with a hash.
|
||||
func DeleteHeader(db DatabaseDeleter, hash common.Hash, number uint64) {
|
||||
db.Delete(append(blockHashPrefix, hash.Bytes()...))
|
||||
db.Delete(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...))
|
||||
}
|
||||
|
||||
// DeleteBody removes all block body data associated with a hash.
|
||||
func DeleteBody(db DatabaseDeleter, hash common.Hash, number uint64) {
|
||||
db.Delete(append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...))
|
||||
}
|
||||
|
||||
// DeleteTd removes all block total difficulty data associated with a hash.
|
||||
func DeleteTd(db DatabaseDeleter, hash common.Hash, number uint64) {
|
||||
db.Delete(append(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...), tdSuffix...))
|
||||
}
|
||||
|
||||
// DeleteBlock removes all block data associated with a hash.
|
||||
func DeleteBlock(db DatabaseDeleter, hash common.Hash, number uint64) {
|
||||
DeleteBlockReceipts(db, hash, number)
|
||||
DeleteHeader(db, hash, number)
|
||||
DeleteBody(db, hash, number)
|
||||
DeleteTd(db, hash, number)
|
||||
}
|
||||
|
||||
// DeleteBlockReceipts removes all receipt data associated with a block hash.
|
||||
func DeleteBlockReceipts(db DatabaseDeleter, hash common.Hash, number uint64) {
|
||||
db.Delete(append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...))
|
||||
}
|
||||
|
||||
// DeleteTxLookupEntry removes all transaction data associated with a hash.
|
||||
func DeleteTxLookupEntry(db DatabaseDeleter, hash common.Hash) {
|
||||
db.Delete(append(lookupPrefix, hash.Bytes()...))
|
||||
}
|
||||
|
||||
// PreimageTable returns a Database instance with the key prefix for preimage entries.
|
||||
func PreimageTable(db ethdb.Database) ethdb.Database {
|
||||
return ethdb.NewTable(db, preimagePrefix)
|
||||
}
|
||||
|
||||
// WritePreimages writes the provided set of preimages to the database. `number` is the
|
||||
// current block number, and is used for debug messages only.
|
||||
func WritePreimages(db ethdb.Database, number uint64, preimages map[common.Hash][]byte) error {
|
||||
table := PreimageTable(db)
|
||||
batch := table.NewBatch()
|
||||
hitCount := 0
|
||||
for hash, preimage := range preimages {
|
||||
if _, err := table.Get(hash.Bytes()); err != nil {
|
||||
batch.Put(hash.Bytes(), preimage)
|
||||
hitCount++
|
||||
}
|
||||
}
|
||||
preimageCounter.Inc(int64(len(preimages)))
|
||||
preimageHitCounter.Inc(int64(hitCount))
|
||||
if hitCount > 0 {
|
||||
if err := batch.Write(); err != nil {
|
||||
return fmt.Errorf("preimage write fail for block %d: %v", number, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlockChainVersion reads the version number from db.
|
||||
func GetBlockChainVersion(db DatabaseReader) int {
|
||||
var vsn uint
|
||||
enc, _ := db.Get([]byte("BlockchainVersion"))
|
||||
rlp.DecodeBytes(enc, &vsn)
|
||||
return int(vsn)
|
||||
}
|
||||
|
||||
// WriteBlockChainVersion writes vsn as the version number to db.
|
||||
func WriteBlockChainVersion(db ethdb.Putter, vsn int) {
|
||||
enc, _ := rlp.EncodeToBytes(uint(vsn))
|
||||
db.Put([]byte("BlockchainVersion"), enc)
|
||||
}
|
||||
|
||||
// WriteChainConfig writes the chain config settings to the database.
|
||||
func WriteChainConfig(db ethdb.Putter, hash common.Hash, cfg *params.ChainConfig) error {
|
||||
// short circuit and ignore if nil config. GetChainConfig
|
||||
// will return a default.
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
jsonChainConfig, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.Put(append(configPrefix, hash[:]...), jsonChainConfig)
|
||||
}
|
||||
|
||||
// GetChainConfig will fetch the network settings based on the given hash.
|
||||
func GetChainConfig(db DatabaseReader, hash common.Hash) (*params.ChainConfig, error) {
|
||||
jsonChainConfig, _ := db.Get(append(configPrefix, hash[:]...))
|
||||
if len(jsonChainConfig) == 0 {
|
||||
return nil, ErrChainConfigNotFound
|
||||
}
|
||||
|
||||
var config params.ChainConfig
|
||||
if err := json.Unmarshal(jsonChainConfig, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// FindCommonAncestor returns the last common ancestor of two block headers
|
||||
func FindCommonAncestor(db DatabaseReader, a, b *types.Header) *types.Header {
|
||||
for bn := b.Number.Uint64(); a.Number.Uint64() > bn; {
|
||||
a = GetHeader(db, a.ParentHash, a.Number.Uint64()-1)
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for an := a.Number.Uint64(); an < b.Number.Uint64(); {
|
||||
b = GetHeader(db, b.ParentHash, b.Number.Uint64()-1)
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for a.Hash() != b.Hash() {
|
||||
a = GetHeader(db, a.ParentHash, a.Number.Uint64()-1)
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
b = GetHeader(db, b.ParentHash, b.Number.Uint64()-1)
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
+2
-8
@@ -21,23 +21,17 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// TxPreEvent is posted when a transaction enters the transaction pool.
|
||||
type TxPreEvent struct{ Tx *types.Transaction }
|
||||
// NewTxsEvent is posted when a batch of transactions enter the transaction pool.
|
||||
type NewTxsEvent struct{ Txs []*types.Transaction }
|
||||
|
||||
// PendingLogsEvent is posted pre mining and notifies of pending logs.
|
||||
type PendingLogsEvent struct {
|
||||
Logs []*types.Log
|
||||
}
|
||||
|
||||
// PendingStateEvent is posted pre mining and notifies of pending state changes.
|
||||
type PendingStateEvent struct{}
|
||||
|
||||
// NewMinedBlockEvent is posted when a block has been imported.
|
||||
type NewMinedBlockEvent struct{ Block *types.Block }
|
||||
|
||||
// RemovedTransactionEvent is posted when a reorg happens
|
||||
type RemovedTransactionEvent struct{ Txs types.Transactions }
|
||||
|
||||
// RemovedLogsEvent is posted when a reorg happens
|
||||
type RemovedLogsEvent struct{ Logs []*types.Log }
|
||||
|
||||
|
||||
+17
-4
@@ -60,18 +60,31 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
|
||||
|
||||
// GetHashFn returns a GetHashFunc which retrieves header hashes by number
|
||||
func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {
|
||||
var cache map[uint64]common.Hash
|
||||
|
||||
return func(n uint64) common.Hash {
|
||||
// If there's no hash cache yet, make one
|
||||
if cache == nil {
|
||||
cache = map[uint64]common.Hash{
|
||||
ref.Number.Uint64() - 1: ref.ParentHash,
|
||||
}
|
||||
}
|
||||
// Try to fulfill the request from the cache
|
||||
if hash, ok := cache[n]; ok {
|
||||
return hash
|
||||
}
|
||||
// Not cached, iterate the blocks and cache the hashes
|
||||
for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {
|
||||
if header.Number.Uint64() == n {
|
||||
return header.Hash()
|
||||
cache[header.Number.Uint64()-1] = header.ParentHash
|
||||
if n == header.Number.Uint64()-1 {
|
||||
return header.ParentHash
|
||||
}
|
||||
}
|
||||
|
||||
return common.Hash{}
|
||||
}
|
||||
}
|
||||
|
||||
// CanTransfer checks wether there are enough funds in the address' account to make a transfer.
|
||||
// CanTransfer checks whether there are enough funds in the address' account to make a transfer.
|
||||
// This does not take the necessary gas in to account to make the transfer valid.
|
||||
func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool {
|
||||
return db.GetBalance(addr).Cmp(amount) >= 0
|
||||
|
||||
+23
-34
@@ -28,6 +28,7 @@ import (
|
||||
"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/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
@@ -155,7 +156,7 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
|
||||
}
|
||||
|
||||
// Just commit the new block if there is no stored genesis block.
|
||||
stored := GetCanonicalHash(db, 0)
|
||||
stored := rawdb.ReadCanonicalHash(db, 0)
|
||||
if (stored == common.Hash{}) {
|
||||
if genesis == nil {
|
||||
log.Info("Writing default main-net genesis block")
|
||||
@@ -177,14 +178,11 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
|
||||
|
||||
// Get the existing chain configuration.
|
||||
newcfg := genesis.configOrDefault(stored)
|
||||
storedcfg, err := GetChainConfig(db, stored)
|
||||
if err != nil {
|
||||
if err == ErrChainConfigNotFound {
|
||||
// This case happens if a genesis write was interrupted.
|
||||
log.Warn("Found genesis block without chain config")
|
||||
err = WriteChainConfig(db, stored, newcfg)
|
||||
}
|
||||
return newcfg, stored, err
|
||||
storedcfg := rawdb.ReadChainConfig(db, stored)
|
||||
if storedcfg == nil {
|
||||
log.Warn("Found genesis block without chain config")
|
||||
rawdb.WriteChainConfig(db, stored, newcfg)
|
||||
return newcfg, stored, nil
|
||||
}
|
||||
// Special case: don't change the existing config of a non-mainnet chain if no new
|
||||
// config is supplied. These chains would get AllProtocolChanges (and a compat error)
|
||||
@@ -195,15 +193,16 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
|
||||
|
||||
// Check config compatibility and write the config. Compatibility errors
|
||||
// are returned to the caller unless we're already at block zero.
|
||||
height := GetBlockNumber(db, GetHeadHeaderHash(db))
|
||||
if height == missingNumber {
|
||||
height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db))
|
||||
if height == nil {
|
||||
return newcfg, stored, fmt.Errorf("missing block number for head header hash")
|
||||
}
|
||||
compatErr := storedcfg.CheckCompatible(newcfg, height)
|
||||
if compatErr != nil && height != 0 && compatErr.RewindTo != 0 {
|
||||
compatErr := storedcfg.CheckCompatible(newcfg, *height)
|
||||
if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 {
|
||||
return newcfg, stored, compatErr
|
||||
}
|
||||
return newcfg, stored, WriteChainConfig(db, stored, newcfg)
|
||||
rawdb.WriteChainConfig(db, stored, newcfg)
|
||||
return newcfg, stored, nil
|
||||
}
|
||||
|
||||
func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
||||
@@ -223,7 +222,7 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
||||
// to the given database (or discards it if nil).
|
||||
func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
|
||||
if db == nil {
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
db = ethdb.NewMemDatabase()
|
||||
}
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
for addr, account := range g.Alloc {
|
||||
@@ -267,29 +266,19 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) {
|
||||
if block.Number().Sign() != 0 {
|
||||
return nil, fmt.Errorf("can't commit genesis block with number > 0")
|
||||
}
|
||||
if err := WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := WriteBlock(db, block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := WriteBlockReceipts(db, block.Hash(), block.NumberU64(), nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := WriteCanonicalHash(db, block.Hash(), block.NumberU64()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := WriteHeadBlockHash(db, block.Hash()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := WriteHeadHeaderHash(db, block.Hash()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawdb.WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty)
|
||||
rawdb.WriteBlock(db, block)
|
||||
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil)
|
||||
rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64())
|
||||
rawdb.WriteHeadBlockHash(db, block.Hash())
|
||||
rawdb.WriteHeadHeaderHash(db, block.Hash())
|
||||
|
||||
config := g.Config
|
||||
if config == nil {
|
||||
config = params.AllEthashProtocolChanges
|
||||
}
|
||||
return block, WriteChainConfig(db, block.Hash(), config)
|
||||
rawdb.WriteChainConfig(db, block.Hash(), config)
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// MustCommit writes the genesis block and state to db, panicking on error.
|
||||
|
||||
+3
-2
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
@@ -140,7 +141,7 @@ func TestSetupGenesis(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
config, hash, err := test.fn(db)
|
||||
// Check the return values.
|
||||
if !reflect.DeepEqual(err, test.wantErr) {
|
||||
@@ -154,7 +155,7 @@ func TestSetupGenesis(t *testing.T) {
|
||||
t.Errorf("%s: returned hash %s, want %s", test.name, hash.Hex(), test.wantHash.Hex())
|
||||
} else if err == nil {
|
||||
// Check database content.
|
||||
stored := GetBlock(db, test.wantHash, 0)
|
||||
stored := rawdb.ReadBlock(db, test.wantHash, 0)
|
||||
if stored.Hash() != test.wantHash {
|
||||
t.Errorf("%s: block in DB has hash %s, want %s", test.name, stored.Hash(), test.wantHash)
|
||||
}
|
||||
|
||||
+86
-43
@@ -23,16 +23,17 @@ import (
|
||||
"math"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/hashicorp/golang-lru"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -97,7 +98,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
||||
}
|
||||
|
||||
hc.currentHeader.Store(hc.genesisHeader)
|
||||
if head := GetHeadBlockHash(chainDb); head != (common.Hash{}) {
|
||||
if head := rawdb.ReadHeadBlockHash(chainDb); head != (common.Hash{}) {
|
||||
if chead := hc.GetHeaderByHash(head); chead != nil {
|
||||
hc.currentHeader.Store(chead)
|
||||
}
|
||||
@@ -109,13 +110,14 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
||||
|
||||
// GetBlockNumber retrieves the block number belonging to the given hash
|
||||
// from the cache or database
|
||||
func (hc *HeaderChain) GetBlockNumber(hash common.Hash) uint64 {
|
||||
func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
|
||||
if cached, ok := hc.numberCache.Get(hash); ok {
|
||||
return cached.(uint64)
|
||||
number := cached.(uint64)
|
||||
return &number
|
||||
}
|
||||
number := GetBlockNumber(hc.chainDb, hash)
|
||||
if number != missingNumber {
|
||||
hc.numberCache.Add(hash, number)
|
||||
number := rawdb.ReadHeaderNumber(hc.chainDb, hash)
|
||||
if number != nil {
|
||||
hc.numberCache.Add(hash, *number)
|
||||
}
|
||||
return number
|
||||
}
|
||||
@@ -147,41 +149,40 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
|
||||
if err := hc.WriteTd(hash, number, externTd); err != nil {
|
||||
log.Crit("Failed to write header total difficulty", "err", err)
|
||||
}
|
||||
if err := WriteHeader(hc.chainDb, header); err != nil {
|
||||
log.Crit("Failed to write header content", "err", err)
|
||||
}
|
||||
rawdb.WriteHeader(hc.chainDb, header)
|
||||
|
||||
// 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
|
||||
if externTd.Cmp(localTd) > 0 || (externTd.Cmp(localTd) == 0 && mrand.Float64() < 0.5) {
|
||||
// Delete any canonical number assignments above the new head
|
||||
batch := hc.chainDb.NewBatch()
|
||||
for i := number + 1; ; i++ {
|
||||
hash := GetCanonicalHash(hc.chainDb, i)
|
||||
hash := rawdb.ReadCanonicalHash(hc.chainDb, i)
|
||||
if hash == (common.Hash{}) {
|
||||
break
|
||||
}
|
||||
DeleteCanonicalHash(hc.chainDb, i)
|
||||
rawdb.DeleteCanonicalHash(batch, i)
|
||||
}
|
||||
batch.Write()
|
||||
|
||||
// Overwrite any stale canonical number assignments
|
||||
var (
|
||||
headHash = header.ParentHash
|
||||
headNumber = header.Number.Uint64() - 1
|
||||
headHeader = hc.GetHeader(headHash, headNumber)
|
||||
)
|
||||
for GetCanonicalHash(hc.chainDb, headNumber) != headHash {
|
||||
WriteCanonicalHash(hc.chainDb, headHash, headNumber)
|
||||
for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash {
|
||||
rawdb.WriteCanonicalHash(hc.chainDb, headHash, headNumber)
|
||||
|
||||
headHash = headHeader.ParentHash
|
||||
headNumber = headHeader.Number.Uint64() - 1
|
||||
headHeader = hc.GetHeader(headHash, headNumber)
|
||||
}
|
||||
// Extend the canonical chain with the new header
|
||||
if err := WriteCanonicalHash(hc.chainDb, hash, number); err != nil {
|
||||
log.Crit("Failed to insert header number", "err", err)
|
||||
}
|
||||
if err := WriteHeadHeaderHash(hc.chainDb, hash); err != nil {
|
||||
log.Crit("Failed to insert head header hash", "err", err)
|
||||
}
|
||||
rawdb.WriteCanonicalHash(hc.chainDb, hash, number)
|
||||
rawdb.WriteHeadHeaderHash(hc.chainDb, hash)
|
||||
|
||||
hc.currentHeaderHash = hash
|
||||
hc.currentHeader.Store(types.CopyHeader(header))
|
||||
|
||||
@@ -207,7 +208,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
|
||||
// Do a sanity check that the provided chain is actually ordered and linked
|
||||
for i := 1; i < len(chain); i++ {
|
||||
if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != chain[i-1].Hash() {
|
||||
// Chain broke ancestry, log a messge (programming error) and skip insertion
|
||||
// Chain broke ancestry, log a message (programming error) and skip insertion
|
||||
log.Error("Non contiguous header insert", "number", chain[i].Number, "hash", chain[i].Hash(),
|
||||
"parent", chain[i].ParentHash, "prevnumber", chain[i-1].Number, "prevhash", chain[i-1].Hash())
|
||||
|
||||
@@ -309,6 +310,43 @@ func (hc *HeaderChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []co
|
||||
return chain
|
||||
}
|
||||
|
||||
// GetAncestor retrieves the Nth ancestor of a given block. It assumes that either the given block or
|
||||
// a close ancestor of it is canonical. maxNonCanonical points to a downwards counter limiting the
|
||||
// number of blocks to be individually checked before we reach the canonical chain.
|
||||
//
|
||||
// Note: ancestor == 0 returns the same block, 1 returns its parent and so on.
|
||||
func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) {
|
||||
if ancestor > number {
|
||||
return common.Hash{}, 0
|
||||
}
|
||||
if ancestor == 1 {
|
||||
// in this case it is cheaper to just read the header
|
||||
if header := hc.GetHeader(hash, number); header != nil {
|
||||
return header.ParentHash, number - 1
|
||||
} else {
|
||||
return common.Hash{}, 0
|
||||
}
|
||||
}
|
||||
for ancestor != 0 {
|
||||
if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash {
|
||||
number -= ancestor
|
||||
return rawdb.ReadCanonicalHash(hc.chainDb, number), number
|
||||
}
|
||||
if *maxNonCanonical == 0 {
|
||||
return common.Hash{}, 0
|
||||
}
|
||||
*maxNonCanonical--
|
||||
ancestor--
|
||||
header := hc.GetHeader(hash, number)
|
||||
if header == nil {
|
||||
return common.Hash{}, 0
|
||||
}
|
||||
hash = header.ParentHash
|
||||
number--
|
||||
}
|
||||
return hash, number
|
||||
}
|
||||
|
||||
// GetTd retrieves a block's total difficulty in the canonical chain from the
|
||||
// database by hash and number, caching it if found.
|
||||
func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
|
||||
@@ -316,7 +354,7 @@ func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
|
||||
if cached, ok := hc.tdCache.Get(hash); ok {
|
||||
return cached.(*big.Int)
|
||||
}
|
||||
td := GetTd(hc.chainDb, hash, number)
|
||||
td := rawdb.ReadTd(hc.chainDb, hash, number)
|
||||
if td == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -328,15 +366,17 @@ func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
|
||||
// GetTdByHash retrieves a block's total difficulty in the canonical chain from the
|
||||
// database by hash, caching it if found.
|
||||
func (hc *HeaderChain) GetTdByHash(hash common.Hash) *big.Int {
|
||||
return hc.GetTd(hash, hc.GetBlockNumber(hash))
|
||||
number := hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
return hc.GetTd(hash, *number)
|
||||
}
|
||||
|
||||
// WriteTd stores a block's total difficulty into the database, also caching it
|
||||
// along the way.
|
||||
func (hc *HeaderChain) WriteTd(hash common.Hash, number uint64, td *big.Int) error {
|
||||
if err := WriteTd(hc.chainDb, hash, number, td); err != nil {
|
||||
return err
|
||||
}
|
||||
rawdb.WriteTd(hc.chainDb, hash, number, td)
|
||||
hc.tdCache.Add(hash, new(big.Int).Set(td))
|
||||
return nil
|
||||
}
|
||||
@@ -348,7 +388,7 @@ func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header
|
||||
if header, ok := hc.headerCache.Get(hash); ok {
|
||||
return header.(*types.Header)
|
||||
}
|
||||
header := GetHeader(hc.chainDb, hash, number)
|
||||
header := rawdb.ReadHeader(hc.chainDb, hash, number)
|
||||
if header == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -360,7 +400,11 @@ func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header
|
||||
// GetHeaderByHash retrieves a block header from the database by hash, caching it if
|
||||
// found.
|
||||
func (hc *HeaderChain) GetHeaderByHash(hash common.Hash) *types.Header {
|
||||
return hc.GetHeader(hash, hc.GetBlockNumber(hash))
|
||||
number := hc.GetBlockNumber(hash)
|
||||
if number == nil {
|
||||
return nil
|
||||
}
|
||||
return hc.GetHeader(hash, *number)
|
||||
}
|
||||
|
||||
// HasHeader checks if a block header is present in the database or not.
|
||||
@@ -368,14 +412,13 @@ func (hc *HeaderChain) HasHeader(hash common.Hash, number uint64) bool {
|
||||
if hc.numberCache.Contains(hash) || hc.headerCache.Contains(hash) {
|
||||
return true
|
||||
}
|
||||
ok, _ := hc.chainDb.Has(headerKey(hash, number))
|
||||
return ok
|
||||
return rawdb.HasHeader(hc.chainDb, hash, number)
|
||||
}
|
||||
|
||||
// GetHeaderByNumber retrieves a block header from the database by number,
|
||||
// caching it (associated with its hash) if found.
|
||||
func (hc *HeaderChain) GetHeaderByNumber(number uint64) *types.Header {
|
||||
hash := GetCanonicalHash(hc.chainDb, number)
|
||||
hash := rawdb.ReadCanonicalHash(hc.chainDb, number)
|
||||
if hash == (common.Hash{}) {
|
||||
return nil
|
||||
}
|
||||
@@ -390,16 +433,15 @@ func (hc *HeaderChain) CurrentHeader() *types.Header {
|
||||
|
||||
// SetCurrentHeader sets the current head header of the canonical chain.
|
||||
func (hc *HeaderChain) SetCurrentHeader(head *types.Header) {
|
||||
if err := WriteHeadHeaderHash(hc.chainDb, head.Hash()); err != nil {
|
||||
log.Crit("Failed to insert head header hash", "err", err)
|
||||
}
|
||||
rawdb.WriteHeadHeaderHash(hc.chainDb, head.Hash())
|
||||
|
||||
hc.currentHeader.Store(head)
|
||||
hc.currentHeaderHash = head.Hash()
|
||||
}
|
||||
|
||||
// DeleteCallback is a callback function that is called by SetHead before
|
||||
// each header is deleted.
|
||||
type DeleteCallback func(common.Hash, uint64)
|
||||
type DeleteCallback func(rawdb.DatabaseDeleter, common.Hash, uint64)
|
||||
|
||||
// SetHead rewinds the local chain to a new head. Everything above the new head
|
||||
// will be deleted and the new one set.
|
||||
@@ -409,21 +451,24 @@ func (hc *HeaderChain) SetHead(head uint64, delFn DeleteCallback) {
|
||||
if hdr := hc.CurrentHeader(); hdr != nil {
|
||||
height = hdr.Number.Uint64()
|
||||
}
|
||||
|
||||
batch := hc.chainDb.NewBatch()
|
||||
for hdr := hc.CurrentHeader(); hdr != nil && hdr.Number.Uint64() > head; hdr = hc.CurrentHeader() {
|
||||
hash := hdr.Hash()
|
||||
num := hdr.Number.Uint64()
|
||||
if delFn != nil {
|
||||
delFn(hash, num)
|
||||
delFn(batch, hash, num)
|
||||
}
|
||||
DeleteHeader(hc.chainDb, hash, num)
|
||||
DeleteTd(hc.chainDb, hash, num)
|
||||
rawdb.DeleteHeader(batch, hash, num)
|
||||
rawdb.DeleteTd(batch, hash, num)
|
||||
|
||||
hc.currentHeader.Store(hc.GetHeader(hdr.ParentHash, hdr.Number.Uint64()-1))
|
||||
}
|
||||
// Roll back the canonical chain numbering
|
||||
for i := height; i > head; i-- {
|
||||
DeleteCanonicalHash(hc.chainDb, i)
|
||||
rawdb.DeleteCanonicalHash(batch, i)
|
||||
}
|
||||
batch.Write()
|
||||
|
||||
// Clear out any stale content from the caches
|
||||
hc.headerCache.Purge()
|
||||
hc.tdCache.Purge()
|
||||
@@ -434,9 +479,7 @@ func (hc *HeaderChain) SetHead(head uint64, delFn DeleteCallback) {
|
||||
}
|
||||
hc.currentHeaderHash = hc.CurrentHeader().Hash()
|
||||
|
||||
if err := WriteHeadHeaderHash(hc.chainDb, hc.currentHeaderHash); err != nil {
|
||||
log.Crit("Failed to reset head header hash", "err", err)
|
||||
}
|
||||
rawdb.WriteHeadHeaderHash(hc.chainDb, hc.currentHeaderHash)
|
||||
}
|
||||
|
||||
// SetGenesis sets a new genesis block header for the chain
|
||||
|
||||
+1
-9
@@ -18,7 +18,6 @@ package core
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
@@ -77,18 +76,11 @@ func (tm *TestManager) Db() ethdb.Database {
|
||||
}
|
||||
|
||||
func NewTestManager() *TestManager {
|
||||
db, err := ethdb.NewMemDatabase()
|
||||
if err != nil {
|
||||
fmt.Println("Could not create mem-db, failing")
|
||||
return nil
|
||||
}
|
||||
|
||||
testManager := &TestManager{}
|
||||
testManager.eventMux = new(event.TypeMux)
|
||||
testManager.db = db
|
||||
testManager.db = ethdb.NewMemDatabase()
|
||||
// testManager.txPool = NewTxPool(testManager)
|
||||
// testManager.blockChain = NewBlockChain(testManager)
|
||||
// testManager.stateManager = NewStateManager(testManager)
|
||||
|
||||
return testManager
|
||||
}
|
||||
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// ReadCanonicalHash retrieves the hash assigned to a canonical block number.
|
||||
func ReadCanonicalHash(db DatabaseReader, number uint64) common.Hash {
|
||||
data, _ := db.Get(headerHashKey(number))
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return common.BytesToHash(data)
|
||||
}
|
||||
|
||||
// WriteCanonicalHash stores the hash assigned to a canonical block number.
|
||||
func WriteCanonicalHash(db DatabaseWriter, hash common.Hash, number uint64) {
|
||||
if err := db.Put(headerHashKey(number), hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to store number to hash mapping", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteCanonicalHash removes the number to hash canonical mapping.
|
||||
func DeleteCanonicalHash(db DatabaseDeleter, number uint64) {
|
||||
if err := db.Delete(headerHashKey(number)); err != nil {
|
||||
log.Crit("Failed to delete number to hash mapping", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadHeaderNumber returns the header number assigned to a hash.
|
||||
func ReadHeaderNumber(db DatabaseReader, hash common.Hash) *uint64 {
|
||||
data, _ := db.Get(headerNumberKey(hash))
|
||||
if len(data) != 8 {
|
||||
return nil
|
||||
}
|
||||
number := binary.BigEndian.Uint64(data)
|
||||
return &number
|
||||
}
|
||||
|
||||
// ReadHeadHeaderHash retrieves the hash of the current canonical head header.
|
||||
func ReadHeadHeaderHash(db DatabaseReader) common.Hash {
|
||||
data, _ := db.Get(headHeaderKey)
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return common.BytesToHash(data)
|
||||
}
|
||||
|
||||
// WriteHeadHeaderHash stores the hash of the current canonical head header.
|
||||
func WriteHeadHeaderHash(db DatabaseWriter, hash common.Hash) {
|
||||
if err := db.Put(headHeaderKey, hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to store last header's hash", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadHeadBlockHash retrieves the hash of the current canonical head block.
|
||||
func ReadHeadBlockHash(db DatabaseReader) common.Hash {
|
||||
data, _ := db.Get(headBlockKey)
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return common.BytesToHash(data)
|
||||
}
|
||||
|
||||
// WriteHeadBlockHash stores the head block's hash.
|
||||
func WriteHeadBlockHash(db DatabaseWriter, hash common.Hash) {
|
||||
if err := db.Put(headBlockKey, hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to store last block's hash", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadHeadFastBlockHash retrieves the hash of the current fast-sync head block.
|
||||
func ReadHeadFastBlockHash(db DatabaseReader) common.Hash {
|
||||
data, _ := db.Get(headFastBlockKey)
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
return common.BytesToHash(data)
|
||||
}
|
||||
|
||||
// WriteHeadFastBlockHash stores the hash of the current fast-sync head block.
|
||||
func WriteHeadFastBlockHash(db DatabaseWriter, hash common.Hash) {
|
||||
if err := db.Put(headFastBlockKey, hash.Bytes()); err != nil {
|
||||
log.Crit("Failed to store last fast block's hash", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadFastTrieProgress retrieves the number of tries nodes fast synced to allow
|
||||
// reporting correct numbers across restarts.
|
||||
func ReadFastTrieProgress(db DatabaseReader) 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 DatabaseWriter, 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)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadHeaderRLP retrieves a block header in its raw RLP database encoding.
|
||||
func ReadHeaderRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue {
|
||||
data, _ := db.Get(headerKey(number, hash))
|
||||
return data
|
||||
}
|
||||
|
||||
// HasHeader verifies the existence of a block header corresponding to the hash.
|
||||
func HasHeader(db DatabaseReader, hash common.Hash, number uint64) bool {
|
||||
if has, err := db.Has(headerKey(number, hash)); !has || err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadHeader retrieves the block header corresponding to the hash.
|
||||
func ReadHeader(db DatabaseReader, hash common.Hash, number uint64) *types.Header {
|
||||
data := ReadHeaderRLP(db, hash, number)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
header := new(types.Header)
|
||||
if err := rlp.Decode(bytes.NewReader(data), header); err != nil {
|
||||
log.Error("Invalid block header RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
// WriteHeader stores a block header into the database and also stores the hash-
|
||||
// to-number mapping.
|
||||
func WriteHeader(db DatabaseWriter, header *types.Header) {
|
||||
// Write the hash -> number mapping
|
||||
var (
|
||||
hash = header.Hash()
|
||||
number = header.Number.Uint64()
|
||||
encoded = encodeBlockNumber(number)
|
||||
)
|
||||
key := headerNumberKey(hash)
|
||||
if err := db.Put(key, encoded); err != nil {
|
||||
log.Crit("Failed to store hash to number mapping", "err", err)
|
||||
}
|
||||
// Write the encoded header
|
||||
data, err := rlp.EncodeToBytes(header)
|
||||
if err != nil {
|
||||
log.Crit("Failed to RLP encode header", "err", err)
|
||||
}
|
||||
key = headerKey(number, hash)
|
||||
if err := db.Put(key, data); err != nil {
|
||||
log.Crit("Failed to store header", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteHeader removes all block header data associated with a hash.
|
||||
func DeleteHeader(db DatabaseDeleter, hash common.Hash, number uint64) {
|
||||
if err := db.Delete(headerKey(number, hash)); err != nil {
|
||||
log.Crit("Failed to delete header", "err", err)
|
||||
}
|
||||
if err := db.Delete(headerNumberKey(hash)); err != nil {
|
||||
log.Crit("Failed to delete hash to number mapping", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadBodyRLP retrieves the block body (transactions and uncles) in RLP encoding.
|
||||
func ReadBodyRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue {
|
||||
data, _ := db.Get(blockBodyKey(number, hash))
|
||||
return data
|
||||
}
|
||||
|
||||
// WriteBodyRLP stores an RLP encoded block body into the database.
|
||||
func WriteBodyRLP(db DatabaseWriter, hash common.Hash, number uint64, rlp rlp.RawValue) {
|
||||
if err := db.Put(blockBodyKey(number, hash), rlp); err != nil {
|
||||
log.Crit("Failed to store block body", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// HasBody verifies the existence of a block body corresponding to the hash.
|
||||
func HasBody(db DatabaseReader, hash common.Hash, number uint64) bool {
|
||||
if has, err := db.Has(blockBodyKey(number, hash)); !has || err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadBody retrieves the block body corresponding to the hash.
|
||||
func ReadBody(db DatabaseReader, hash common.Hash, number uint64) *types.Body {
|
||||
data := ReadBodyRLP(db, hash, number)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
body := new(types.Body)
|
||||
if err := rlp.Decode(bytes.NewReader(data), body); err != nil {
|
||||
log.Error("Invalid block body RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
// WriteBody storea a block body into the database.
|
||||
func WriteBody(db DatabaseWriter, hash common.Hash, number uint64, body *types.Body) {
|
||||
data, err := rlp.EncodeToBytes(body)
|
||||
if err != nil {
|
||||
log.Crit("Failed to RLP encode body", "err", err)
|
||||
}
|
||||
WriteBodyRLP(db, hash, number, data)
|
||||
}
|
||||
|
||||
// DeleteBody removes all block body data associated with a hash.
|
||||
func DeleteBody(db DatabaseDeleter, hash common.Hash, number uint64) {
|
||||
if err := db.Delete(blockBodyKey(number, hash)); err != nil {
|
||||
log.Crit("Failed to delete block body", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadTd retrieves a block's total difficulty corresponding to the hash.
|
||||
func ReadTd(db DatabaseReader, hash common.Hash, number uint64) *big.Int {
|
||||
data, _ := db.Get(headerTDKey(number, hash))
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
td := new(big.Int)
|
||||
if err := rlp.Decode(bytes.NewReader(data), td); err != nil {
|
||||
log.Error("Invalid block total difficulty RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
return td
|
||||
}
|
||||
|
||||
// WriteTd stores the total difficulty of a block into the database.
|
||||
func WriteTd(db DatabaseWriter, hash common.Hash, number uint64, td *big.Int) {
|
||||
data, err := rlp.EncodeToBytes(td)
|
||||
if err != nil {
|
||||
log.Crit("Failed to RLP encode block total difficulty", "err", err)
|
||||
}
|
||||
if err := db.Put(headerTDKey(number, hash), data); err != nil {
|
||||
log.Crit("Failed to store block total difficulty", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTd removes all block total difficulty data associated with a hash.
|
||||
func DeleteTd(db DatabaseDeleter, hash common.Hash, number uint64) {
|
||||
if err := db.Delete(headerTDKey(number, hash)); err != nil {
|
||||
log.Crit("Failed to delete block total difficulty", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadReceipts retrieves all the transaction receipts belonging to a block.
|
||||
func ReadReceipts(db DatabaseReader, hash common.Hash, number uint64) types.Receipts {
|
||||
// Retrieve the flattened receipt slice
|
||||
data, _ := db.Get(blockReceiptsKey(number, hash))
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Convert the revceipts from their storage form to their internal representation
|
||||
storageReceipts := []*types.ReceiptForStorage{}
|
||||
if err := rlp.DecodeBytes(data, &storageReceipts); err != nil {
|
||||
log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
receipts := make(types.Receipts, len(storageReceipts))
|
||||
for i, receipt := range storageReceipts {
|
||||
receipts[i] = (*types.Receipt)(receipt)
|
||||
}
|
||||
return receipts
|
||||
}
|
||||
|
||||
// WriteReceipts stores all the transaction receipts belonging to a block.
|
||||
func WriteReceipts(db DatabaseWriter, hash common.Hash, number uint64, receipts types.Receipts) {
|
||||
// Convert the receipts into their storage form and serialize them
|
||||
storageReceipts := make([]*types.ReceiptForStorage, len(receipts))
|
||||
for i, receipt := range receipts {
|
||||
storageReceipts[i] = (*types.ReceiptForStorage)(receipt)
|
||||
}
|
||||
bytes, err := rlp.EncodeToBytes(storageReceipts)
|
||||
if err != nil {
|
||||
log.Crit("Failed to encode block receipts", "err", err)
|
||||
}
|
||||
// Store the flattened receipt slice
|
||||
if err := db.Put(blockReceiptsKey(number, hash), bytes); err != nil {
|
||||
log.Crit("Failed to store block receipts", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteReceipts removes all receipt data associated with a block hash.
|
||||
func DeleteReceipts(db DatabaseDeleter, hash common.Hash, number uint64) {
|
||||
if err := db.Delete(blockReceiptsKey(number, hash)); err != nil {
|
||||
log.Crit("Failed to delete block receipts", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadBlock retrieves an entire block corresponding to the hash, assembling it
|
||||
// back from the stored header and body. If either the header or body could not
|
||||
// be retrieved nil is returned.
|
||||
//
|
||||
// Note, due to concurrent download of header and block body the header and thus
|
||||
// canonical hash can be stored in the database but the body data not (yet).
|
||||
func ReadBlock(db DatabaseReader, hash common.Hash, number uint64) *types.Block {
|
||||
header := ReadHeader(db, hash, number)
|
||||
if header == nil {
|
||||
return nil
|
||||
}
|
||||
body := ReadBody(db, hash, number)
|
||||
if body == nil {
|
||||
return nil
|
||||
}
|
||||
return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles)
|
||||
}
|
||||
|
||||
// WriteBlock serializes a block into the database, header and body separately.
|
||||
func WriteBlock(db DatabaseWriter, block *types.Block) {
|
||||
WriteBody(db, block.Hash(), block.NumberU64(), block.Body())
|
||||
WriteHeader(db, block.Header())
|
||||
}
|
||||
|
||||
// DeleteBlock removes all block data associated with a hash.
|
||||
func DeleteBlock(db DatabaseDeleter, hash common.Hash, number uint64) {
|
||||
DeleteReceipts(db, hash, number)
|
||||
DeleteHeader(db, hash, number)
|
||||
DeleteBody(db, hash, number)
|
||||
DeleteTd(db, hash, number)
|
||||
}
|
||||
|
||||
// FindCommonAncestor returns the last common ancestor of two block headers
|
||||
func FindCommonAncestor(db DatabaseReader, a, b *types.Header) *types.Header {
|
||||
for bn := b.Number.Uint64(); a.Number.Uint64() > bn; {
|
||||
a = ReadHeader(db, a.ParentHash, a.Number.Uint64()-1)
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for an := a.Number.Uint64(); an < b.Number.Uint64(); {
|
||||
b = ReadHeader(db, b.ParentHash, b.Number.Uint64()-1)
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
for a.Hash() != b.Hash() {
|
||||
a = ReadHeader(db, a.ParentHash, a.Number.Uint64()-1)
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
b = ReadHeader(db, b.ParentHash, b.Number.Uint64()-1)
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
Generated
Vendored
+61
-130
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
@@ -14,7 +14,7 @@
|
||||
// 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
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -30,23 +30,21 @@ import (
|
||||
|
||||
// Tests block header storage and retrieval operations.
|
||||
func TestHeaderStorage(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
|
||||
// Create a test header to move around the database and make sure it's really new
|
||||
header := &types.Header{Number: big.NewInt(42), Extra: []byte("test header")}
|
||||
if entry := GetHeader(db, header.Hash(), header.Number.Uint64()); entry != nil {
|
||||
if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry != nil {
|
||||
t.Fatalf("Non existent header returned: %v", entry)
|
||||
}
|
||||
// Write and verify the header in the database
|
||||
if err := WriteHeader(db, header); err != nil {
|
||||
t.Fatalf("Failed to write header into database: %v", err)
|
||||
}
|
||||
if entry := GetHeader(db, header.Hash(), header.Number.Uint64()); entry == nil {
|
||||
WriteHeader(db, header)
|
||||
if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry == nil {
|
||||
t.Fatalf("Stored header not found")
|
||||
} else if entry.Hash() != header.Hash() {
|
||||
t.Fatalf("Retrieved header mismatch: have %v, want %v", entry, header)
|
||||
}
|
||||
if entry := GetHeaderRLP(db, header.Hash(), header.Number.Uint64()); entry == nil {
|
||||
if entry := ReadHeaderRLP(db, header.Hash(), header.Number.Uint64()); entry == nil {
|
||||
t.Fatalf("Stored header RLP not found")
|
||||
} else {
|
||||
hasher := sha3.NewKeccak256()
|
||||
@@ -58,14 +56,14 @@ func TestHeaderStorage(t *testing.T) {
|
||||
}
|
||||
// Delete the header and verify the execution
|
||||
DeleteHeader(db, header.Hash(), header.Number.Uint64())
|
||||
if entry := GetHeader(db, header.Hash(), header.Number.Uint64()); entry != nil {
|
||||
if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry != nil {
|
||||
t.Fatalf("Deleted header returned: %v", entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests block body storage and retrieval operations.
|
||||
func TestBodyStorage(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
|
||||
// Create a test body to move around the database and make sure it's really new
|
||||
body := &types.Body{Uncles: []*types.Header{{Extra: []byte("test header")}}}
|
||||
@@ -74,19 +72,17 @@ func TestBodyStorage(t *testing.T) {
|
||||
rlp.Encode(hasher, body)
|
||||
hash := common.BytesToHash(hasher.Sum(nil))
|
||||
|
||||
if entry := GetBody(db, hash, 0); entry != nil {
|
||||
if entry := ReadBody(db, hash, 0); entry != nil {
|
||||
t.Fatalf("Non existent body returned: %v", entry)
|
||||
}
|
||||
// Write and verify the body in the database
|
||||
if err := WriteBody(db, hash, 0, body); err != nil {
|
||||
t.Fatalf("Failed to write body into database: %v", err)
|
||||
}
|
||||
if entry := GetBody(db, hash, 0); entry == nil {
|
||||
WriteBody(db, hash, 0, body)
|
||||
if entry := ReadBody(db, hash, 0); entry == nil {
|
||||
t.Fatalf("Stored body not found")
|
||||
} else if types.DeriveSha(types.Transactions(entry.Transactions)) != types.DeriveSha(types.Transactions(body.Transactions)) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) {
|
||||
t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, body)
|
||||
}
|
||||
if entry := GetBodyRLP(db, hash, 0); entry == nil {
|
||||
if entry := ReadBodyRLP(db, hash, 0); entry == nil {
|
||||
t.Fatalf("Stored body RLP not found")
|
||||
} else {
|
||||
hasher := sha3.NewKeccak256()
|
||||
@@ -98,14 +94,14 @@ func TestBodyStorage(t *testing.T) {
|
||||
}
|
||||
// Delete the body and verify the execution
|
||||
DeleteBody(db, hash, 0)
|
||||
if entry := GetBody(db, hash, 0); entry != nil {
|
||||
if entry := ReadBody(db, hash, 0); entry != nil {
|
||||
t.Fatalf("Deleted body returned: %v", entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests block storage and retrieval operations.
|
||||
func TestBlockStorage(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
|
||||
// Create a test block to move around the database and make sure it's really new
|
||||
block := types.NewBlockWithHeader(&types.Header{
|
||||
@@ -114,50 +110,48 @@ func TestBlockStorage(t *testing.T) {
|
||||
TxHash: types.EmptyRootHash,
|
||||
ReceiptHash: types.EmptyRootHash,
|
||||
})
|
||||
if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
t.Fatalf("Non existent block returned: %v", entry)
|
||||
}
|
||||
if entry := GetHeader(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
t.Fatalf("Non existent header returned: %v", entry)
|
||||
}
|
||||
if entry := GetBody(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
t.Fatalf("Non existent body returned: %v", entry)
|
||||
}
|
||||
// Write and verify the block in the database
|
||||
if err := WriteBlock(db, block); err != nil {
|
||||
t.Fatalf("Failed to write block into database: %v", err)
|
||||
}
|
||||
if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry == nil {
|
||||
WriteBlock(db, block)
|
||||
if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry == nil {
|
||||
t.Fatalf("Stored block not found")
|
||||
} else if entry.Hash() != block.Hash() {
|
||||
t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block)
|
||||
}
|
||||
if entry := GetHeader(db, block.Hash(), block.NumberU64()); entry == nil {
|
||||
if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry == nil {
|
||||
t.Fatalf("Stored header not found")
|
||||
} else if entry.Hash() != block.Header().Hash() {
|
||||
t.Fatalf("Retrieved header mismatch: have %v, want %v", entry, block.Header())
|
||||
}
|
||||
if entry := GetBody(db, block.Hash(), block.NumberU64()); entry == nil {
|
||||
if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry == nil {
|
||||
t.Fatalf("Stored body not found")
|
||||
} else if types.DeriveSha(types.Transactions(entry.Transactions)) != types.DeriveSha(block.Transactions()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) {
|
||||
t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, block.Body())
|
||||
}
|
||||
// Delete the block and verify the execution
|
||||
DeleteBlock(db, block.Hash(), block.NumberU64())
|
||||
if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
t.Fatalf("Deleted block returned: %v", entry)
|
||||
}
|
||||
if entry := GetHeader(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
t.Fatalf("Deleted header returned: %v", entry)
|
||||
}
|
||||
if entry := GetBody(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
t.Fatalf("Deleted body returned: %v", entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that partial block contents don't get reassembled into full blocks.
|
||||
func TestPartialBlockStorage(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
block := types.NewBlockWithHeader(&types.Header{
|
||||
Extra: []byte("test block"),
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
@@ -165,31 +159,24 @@ func TestPartialBlockStorage(t *testing.T) {
|
||||
ReceiptHash: types.EmptyRootHash,
|
||||
})
|
||||
// Store a header and check that it's not recognized as a block
|
||||
if err := WriteHeader(db, block.Header()); err != nil {
|
||||
t.Fatalf("Failed to write header into database: %v", err)
|
||||
}
|
||||
if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
WriteHeader(db, block.Header())
|
||||
if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
t.Fatalf("Non existent block returned: %v", entry)
|
||||
}
|
||||
DeleteHeader(db, block.Hash(), block.NumberU64())
|
||||
|
||||
// Store a body and check that it's not recognized as a block
|
||||
if err := WriteBody(db, block.Hash(), block.NumberU64(), block.Body()); err != nil {
|
||||
t.Fatalf("Failed to write body into database: %v", err)
|
||||
}
|
||||
if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
WriteBody(db, block.Hash(), block.NumberU64(), block.Body())
|
||||
if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil {
|
||||
t.Fatalf("Non existent block returned: %v", entry)
|
||||
}
|
||||
DeleteBody(db, block.Hash(), block.NumberU64())
|
||||
|
||||
// Store a header and a body separately and check reassembly
|
||||
if err := WriteHeader(db, block.Header()); err != nil {
|
||||
t.Fatalf("Failed to write header into database: %v", err)
|
||||
}
|
||||
if err := WriteBody(db, block.Hash(), block.NumberU64(), block.Body()); err != nil {
|
||||
t.Fatalf("Failed to write body into database: %v", err)
|
||||
}
|
||||
if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry == nil {
|
||||
WriteHeader(db, block.Header())
|
||||
WriteBody(db, block.Hash(), block.NumberU64(), block.Body())
|
||||
|
||||
if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry == nil {
|
||||
t.Fatalf("Stored block not found")
|
||||
} else if entry.Hash() != block.Hash() {
|
||||
t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block)
|
||||
@@ -198,142 +185,88 @@ func TestPartialBlockStorage(t *testing.T) {
|
||||
|
||||
// Tests block total difficulty storage and retrieval operations.
|
||||
func TestTdStorage(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
|
||||
// Create a test TD to move around the database and make sure it's really new
|
||||
hash, td := common.Hash{}, big.NewInt(314)
|
||||
if entry := GetTd(db, hash, 0); entry != nil {
|
||||
if entry := ReadTd(db, hash, 0); entry != nil {
|
||||
t.Fatalf("Non existent TD returned: %v", entry)
|
||||
}
|
||||
// Write and verify the TD in the database
|
||||
if err := WriteTd(db, hash, 0, td); err != nil {
|
||||
t.Fatalf("Failed to write TD into database: %v", err)
|
||||
}
|
||||
if entry := GetTd(db, hash, 0); entry == nil {
|
||||
WriteTd(db, hash, 0, td)
|
||||
if entry := ReadTd(db, hash, 0); entry == nil {
|
||||
t.Fatalf("Stored TD not found")
|
||||
} else if entry.Cmp(td) != 0 {
|
||||
t.Fatalf("Retrieved TD mismatch: have %v, want %v", entry, td)
|
||||
}
|
||||
// Delete the TD and verify the execution
|
||||
DeleteTd(db, hash, 0)
|
||||
if entry := GetTd(db, hash, 0); entry != nil {
|
||||
if entry := ReadTd(db, hash, 0); entry != nil {
|
||||
t.Fatalf("Deleted TD returned: %v", entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that canonical numbers can be mapped to hashes and retrieved.
|
||||
func TestCanonicalMappingStorage(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
|
||||
// Create a test canonical number and assinged hash to move around
|
||||
hash, number := common.Hash{0: 0xff}, uint64(314)
|
||||
if entry := GetCanonicalHash(db, number); entry != (common.Hash{}) {
|
||||
if entry := ReadCanonicalHash(db, number); entry != (common.Hash{}) {
|
||||
t.Fatalf("Non existent canonical mapping returned: %v", entry)
|
||||
}
|
||||
// Write and verify the TD in the database
|
||||
if err := WriteCanonicalHash(db, hash, number); err != nil {
|
||||
t.Fatalf("Failed to write canonical mapping into database: %v", err)
|
||||
}
|
||||
if entry := GetCanonicalHash(db, number); entry == (common.Hash{}) {
|
||||
WriteCanonicalHash(db, hash, number)
|
||||
if entry := ReadCanonicalHash(db, number); entry == (common.Hash{}) {
|
||||
t.Fatalf("Stored canonical mapping not found")
|
||||
} else if entry != hash {
|
||||
t.Fatalf("Retrieved canonical mapping mismatch: have %v, want %v", entry, hash)
|
||||
}
|
||||
// Delete the TD and verify the execution
|
||||
DeleteCanonicalHash(db, number)
|
||||
if entry := GetCanonicalHash(db, number); entry != (common.Hash{}) {
|
||||
if entry := ReadCanonicalHash(db, number); entry != (common.Hash{}) {
|
||||
t.Fatalf("Deleted canonical mapping returned: %v", entry)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that head headers and head blocks can be assigned, individually.
|
||||
func TestHeadStorage(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
|
||||
blockHead := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block header")})
|
||||
blockFull := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block full")})
|
||||
blockFast := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block fast")})
|
||||
|
||||
// Check that no head entries are in a pristine database
|
||||
if entry := GetHeadHeaderHash(db); entry != (common.Hash{}) {
|
||||
if entry := ReadHeadHeaderHash(db); entry != (common.Hash{}) {
|
||||
t.Fatalf("Non head header entry returned: %v", entry)
|
||||
}
|
||||
if entry := GetHeadBlockHash(db); entry != (common.Hash{}) {
|
||||
if entry := ReadHeadBlockHash(db); entry != (common.Hash{}) {
|
||||
t.Fatalf("Non head block entry returned: %v", entry)
|
||||
}
|
||||
if entry := GetHeadFastBlockHash(db); entry != (common.Hash{}) {
|
||||
if entry := ReadHeadFastBlockHash(db); entry != (common.Hash{}) {
|
||||
t.Fatalf("Non fast head block entry returned: %v", entry)
|
||||
}
|
||||
// Assign separate entries for the head header and block
|
||||
if err := WriteHeadHeaderHash(db, blockHead.Hash()); err != nil {
|
||||
t.Fatalf("Failed to write head header hash: %v", err)
|
||||
}
|
||||
if err := WriteHeadBlockHash(db, blockFull.Hash()); err != nil {
|
||||
t.Fatalf("Failed to write head block hash: %v", err)
|
||||
}
|
||||
if err := WriteHeadFastBlockHash(db, blockFast.Hash()); err != nil {
|
||||
t.Fatalf("Failed to write fast head block hash: %v", err)
|
||||
}
|
||||
WriteHeadHeaderHash(db, blockHead.Hash())
|
||||
WriteHeadBlockHash(db, blockFull.Hash())
|
||||
WriteHeadFastBlockHash(db, blockFast.Hash())
|
||||
|
||||
// Check that both heads are present, and different (i.e. two heads maintained)
|
||||
if entry := GetHeadHeaderHash(db); entry != blockHead.Hash() {
|
||||
if entry := ReadHeadHeaderHash(db); entry != blockHead.Hash() {
|
||||
t.Fatalf("Head header hash mismatch: have %v, want %v", entry, blockHead.Hash())
|
||||
}
|
||||
if entry := GetHeadBlockHash(db); entry != blockFull.Hash() {
|
||||
if entry := ReadHeadBlockHash(db); entry != blockFull.Hash() {
|
||||
t.Fatalf("Head block hash mismatch: have %v, want %v", entry, blockFull.Hash())
|
||||
}
|
||||
if entry := GetHeadFastBlockHash(db); entry != blockFast.Hash() {
|
||||
if entry := ReadHeadFastBlockHash(db); entry != blockFast.Hash() {
|
||||
t.Fatalf("Fast head block hash mismatch: have %v, want %v", entry, blockFast.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that positional lookup metadata can be stored and retrieved.
|
||||
func TestLookupStorage(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
|
||||
tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11})
|
||||
tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22})
|
||||
tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33})
|
||||
txs := []*types.Transaction{tx1, tx2, tx3}
|
||||
|
||||
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil)
|
||||
|
||||
// Check that no transactions entries are in a pristine database
|
||||
for i, tx := range txs {
|
||||
if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn != nil {
|
||||
t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn)
|
||||
}
|
||||
}
|
||||
// Insert all the transactions into the database, and verify contents
|
||||
if err := WriteBlock(db, block); err != nil {
|
||||
t.Fatalf("failed to write block contents: %v", err)
|
||||
}
|
||||
if err := WriteTxLookupEntries(db, block); err != nil {
|
||||
t.Fatalf("failed to write transactions: %v", err)
|
||||
}
|
||||
for i, tx := range txs {
|
||||
if txn, hash, number, index := GetTransaction(db, tx.Hash()); txn == nil {
|
||||
t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash())
|
||||
} else {
|
||||
if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) {
|
||||
t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, tx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), i)
|
||||
}
|
||||
if tx.String() != txn.String() {
|
||||
t.Fatalf("tx #%d [%x]: transaction mismatch: have %v, want %v", i, tx.Hash(), txn, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete the transactions and check purge
|
||||
for i, tx := range txs {
|
||||
DeleteTxLookupEntry(db, tx.Hash())
|
||||
if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn != nil {
|
||||
t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that receipts associated with a single block can be stored and retrieved.
|
||||
func TestBlockReceiptStorage(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
|
||||
receipt1 := &types.Receipt{
|
||||
Status: types.ReceiptStatusFailed,
|
||||
@@ -361,14 +294,12 @@ func TestBlockReceiptStorage(t *testing.T) {
|
||||
|
||||
// Check that no receipt entries are in a pristine database
|
||||
hash := common.BytesToHash([]byte{0x03, 0x14})
|
||||
if rs := GetBlockReceipts(db, hash, 0); len(rs) != 0 {
|
||||
if rs := ReadReceipts(db, hash, 0); len(rs) != 0 {
|
||||
t.Fatalf("non existent receipts returned: %v", rs)
|
||||
}
|
||||
// Insert the receipt slice into the database and check presence
|
||||
if err := WriteBlockReceipts(db, hash, 0, receipts); err != nil {
|
||||
t.Fatalf("failed to write block receipts: %v", err)
|
||||
}
|
||||
if rs := GetBlockReceipts(db, hash, 0); len(rs) == 0 {
|
||||
WriteReceipts(db, hash, 0, receipts)
|
||||
if rs := ReadReceipts(db, hash, 0); len(rs) == 0 {
|
||||
t.Fatalf("no receipts returned")
|
||||
} else {
|
||||
for i := 0; i < len(receipts); i++ {
|
||||
@@ -381,8 +312,8 @@ func TestBlockReceiptStorage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// Delete the receipt slice and check purge
|
||||
DeleteBlockReceipts(db, hash, 0)
|
||||
if rs := GetBlockReceipts(db, hash, 0); len(rs) != 0 {
|
||||
DeleteReceipts(db, hash, 0)
|
||||
if rs := ReadReceipts(db, hash, 0); len(rs) != 0 {
|
||||
t.Fatalf("deleted receipts returned: %v", rs)
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// ReadTxLookupEntry retrieves the positional metadata associated with a transaction
|
||||
// hash to allow retrieving the transaction or receipt by hash.
|
||||
func ReadTxLookupEntry(db DatabaseReader, hash common.Hash) (common.Hash, uint64, uint64) {
|
||||
data, _ := db.Get(txLookupKey(hash))
|
||||
if len(data) == 0 {
|
||||
return common.Hash{}, 0, 0
|
||||
}
|
||||
var entry TxLookupEntry
|
||||
if err := rlp.DecodeBytes(data, &entry); err != nil {
|
||||
log.Error("Invalid transaction lookup entry RLP", "hash", hash, "err", err)
|
||||
return common.Hash{}, 0, 0
|
||||
}
|
||||
return entry.BlockHash, entry.BlockIndex, entry.Index
|
||||
}
|
||||
|
||||
// WriteTxLookupEntries stores a positional metadata for every transaction from
|
||||
// a block, enabling hash based transaction and receipt lookups.
|
||||
func WriteTxLookupEntries(db DatabaseWriter, block *types.Block) {
|
||||
for i, tx := range block.Transactions() {
|
||||
entry := TxLookupEntry{
|
||||
BlockHash: block.Hash(),
|
||||
BlockIndex: block.NumberU64(),
|
||||
Index: uint64(i),
|
||||
}
|
||||
data, err := rlp.EncodeToBytes(entry)
|
||||
if err != nil {
|
||||
log.Crit("Failed to encode transaction lookup entry", "err", err)
|
||||
}
|
||||
if err := db.Put(txLookupKey(tx.Hash()), data); err != nil {
|
||||
log.Crit("Failed to store transaction lookup entry", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteTxLookupEntry removes all transaction data associated with a hash.
|
||||
func DeleteTxLookupEntry(db DatabaseDeleter, hash common.Hash) {
|
||||
db.Delete(txLookupKey(hash))
|
||||
}
|
||||
|
||||
// ReadTransaction retrieves a specific transaction from the database, along with
|
||||
// its added positional metadata.
|
||||
func ReadTransaction(db DatabaseReader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) {
|
||||
blockHash, blockNumber, txIndex := ReadTxLookupEntry(db, hash)
|
||||
if blockHash == (common.Hash{}) {
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
body := ReadBody(db, blockHash, blockNumber)
|
||||
if body == nil || len(body.Transactions) <= int(txIndex) {
|
||||
log.Error("Transaction referenced missing", "number", blockNumber, "hash", blockHash, "index", txIndex)
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
return body.Transactions[txIndex], blockHash, blockNumber, txIndex
|
||||
}
|
||||
|
||||
// ReadReceipt retrieves a specific transaction receipt from the database, along with
|
||||
// its added positional metadata.
|
||||
func ReadReceipt(db DatabaseReader, hash common.Hash) (*types.Receipt, common.Hash, uint64, uint64) {
|
||||
blockHash, blockNumber, receiptIndex := ReadTxLookupEntry(db, hash)
|
||||
if blockHash == (common.Hash{}) {
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
receipts := ReadReceipts(db, blockHash, blockNumber)
|
||||
if len(receipts) <= int(receiptIndex) {
|
||||
log.Error("Receipt refereced missing", "number", blockNumber, "hash", blockHash, "index", receiptIndex)
|
||||
return nil, common.Hash{}, 0, 0
|
||||
}
|
||||
return receipts[receiptIndex], blockHash, blockNumber, receiptIndex
|
||||
}
|
||||
|
||||
// ReadBloomBits retrieves the compressed bloom bit vector belonging to the given
|
||||
// section and bit index from the.
|
||||
func ReadBloomBits(db DatabaseReader, bit uint, section uint64, head common.Hash) ([]byte, error) {
|
||||
return db.Get(bloomBitsKey(bit, section, head))
|
||||
}
|
||||
|
||||
// WriteBloomBits stores the compressed bloom bits vector belonging to the given
|
||||
// section and bit index.
|
||||
func WriteBloomBits(db DatabaseWriter, bit uint, section uint64, head common.Hash, bits []byte) {
|
||||
if err := db.Put(bloomBitsKey(bit, section, head), bits); err != nil {
|
||||
log.Crit("Failed to store bloom bits", "err", err)
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
)
|
||||
|
||||
// Tests that positional lookup metadata can be stored and retrieved.
|
||||
func TestLookupStorage(t *testing.T) {
|
||||
db := ethdb.NewMemDatabase()
|
||||
|
||||
tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11})
|
||||
tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22})
|
||||
tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33})
|
||||
txs := []*types.Transaction{tx1, tx2, tx3}
|
||||
|
||||
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil)
|
||||
|
||||
// Check that no transactions entries are in a pristine database
|
||||
for i, tx := range txs {
|
||||
if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil {
|
||||
t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn)
|
||||
}
|
||||
}
|
||||
// Insert all the transactions into the database, and verify contents
|
||||
WriteBlock(db, block)
|
||||
WriteTxLookupEntries(db, block)
|
||||
|
||||
for i, tx := range txs {
|
||||
if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil {
|
||||
t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash())
|
||||
} else {
|
||||
if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) {
|
||||
t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, tx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), i)
|
||||
}
|
||||
if tx.Hash() != txn.Hash() {
|
||||
t.Fatalf("tx #%d [%x]: transaction mismatch: have %v, want %v", i, tx.Hash(), txn, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete the transactions and check purge
|
||||
for i, tx := range txs {
|
||||
DeleteTxLookupEntry(db, tx.Hash())
|
||||
if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil {
|
||||
t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn)
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// ReadDatabaseVersion retrieves the version number of the database.
|
||||
func ReadDatabaseVersion(db DatabaseReader) int {
|
||||
var version int
|
||||
|
||||
enc, _ := db.Get(databaseVerisionKey)
|
||||
rlp.DecodeBytes(enc, &version)
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// WriteDatabaseVersion stores the version number of the database
|
||||
func WriteDatabaseVersion(db DatabaseWriter, version int) {
|
||||
enc, _ := rlp.EncodeToBytes(version)
|
||||
if err := db.Put(databaseVerisionKey, enc); err != nil {
|
||||
log.Crit("Failed to store the database version", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadChainConfig retrieves the consensus settings based on the given genesis hash.
|
||||
func ReadChainConfig(db DatabaseReader, hash common.Hash) *params.ChainConfig {
|
||||
data, _ := db.Get(configKey(hash))
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
var config params.ChainConfig
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
log.Error("Invalid chain config JSON", "hash", hash, "err", err)
|
||||
return nil
|
||||
}
|
||||
return &config
|
||||
}
|
||||
|
||||
// WriteChainConfig writes the chain config settings to the database.
|
||||
func WriteChainConfig(db DatabaseWriter, hash common.Hash, cfg *params.ChainConfig) {
|
||||
if cfg == nil {
|
||||
return
|
||||
}
|
||||
data, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
log.Crit("Failed to JSON encode chain config", "err", err)
|
||||
}
|
||||
if err := db.Put(configKey(hash), data); err != nil {
|
||||
log.Crit("Failed to store chain config", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadPreimage retrieves a single preimage of the provided hash.
|
||||
func ReadPreimage(db DatabaseReader, hash common.Hash) []byte {
|
||||
data, _ := db.Get(preimageKey(hash))
|
||||
return data
|
||||
}
|
||||
|
||||
// WritePreimages writes the provided set of preimages to the database. `number` is the
|
||||
// current block number, and is used for debug messages only.
|
||||
func WritePreimages(db DatabaseWriter, number uint64, preimages map[common.Hash][]byte) {
|
||||
for hash, preimage := range preimages {
|
||||
if err := db.Put(preimageKey(hash), preimage); err != nil {
|
||||
log.Crit("Failed to store trie preimage", "err", err)
|
||||
}
|
||||
}
|
||||
preimageCounter.Inc(int64(len(preimages)))
|
||||
preimageHitCounter.Inc(int64(len(preimages)))
|
||||
}
|
||||
Generated
Vendored
+17
-3
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
@@ -14,6 +14,20 @@
|
||||
// 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/>.
|
||||
|
||||
// +build !evmjit
|
||||
package rawdb
|
||||
|
||||
package vm
|
||||
// DatabaseReader wraps the Has and Get method of a backing data store.
|
||||
type DatabaseReader interface {
|
||||
Has(key []byte) (bool, error)
|
||||
Get(key []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// DatabaseWriter wraps the Put method of a backing data store.
|
||||
type DatabaseWriter interface {
|
||||
Put(key []byte, value []byte) error
|
||||
}
|
||||
|
||||
// DatabaseDeleter wraps the Delete method of a backing data store.
|
||||
type DatabaseDeleter interface {
|
||||
Delete(key []byte) error
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package rawdb contains a collection of low level database accessors.
|
||||
package rawdb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
// The fields below define the low level database schema prefixing.
|
||||
var (
|
||||
// databaseVerisionKey tracks the current database version.
|
||||
databaseVerisionKey = []byte("DatabaseVersion")
|
||||
|
||||
// headHeaderKey tracks the latest know header's hash.
|
||||
headHeaderKey = []byte("LastHeader")
|
||||
|
||||
// headBlockKey tracks the latest know full block's hash.
|
||||
headBlockKey = []byte("LastBlock")
|
||||
|
||||
// headFastBlockKey tracks the latest known incomplete block's hash duirng fast sync.
|
||||
headFastBlockKey = []byte("LastFast")
|
||||
|
||||
// fastTrieProgressKey tracks the number of trie entries imported during fast sync.
|
||||
fastTrieProgressKey = []byte("TrieSync")
|
||||
|
||||
// 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
|
||||
headerHashSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + headerHashSuffix -> hash
|
||||
headerNumberPrefix = []byte("H") // headerNumberPrefix + hash -> num (uint64 big endian)
|
||||
|
||||
blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body
|
||||
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
|
||||
|
||||
txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata
|
||||
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
|
||||
|
||||
preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage
|
||||
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
||||
|
||||
// Chain index prefixes (use `i` + single byte to avoid mixing data types).
|
||||
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
|
||||
|
||||
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
|
||||
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
||||
)
|
||||
|
||||
// TxLookupEntry is a positional metadata to help looking up the data content of
|
||||
// a transaction or receipt given only its hash.
|
||||
type TxLookupEntry struct {
|
||||
BlockHash common.Hash
|
||||
BlockIndex uint64
|
||||
Index uint64
|
||||
}
|
||||
|
||||
// encodeBlockNumber encodes a block number as big endian uint64
|
||||
func encodeBlockNumber(number uint64) []byte {
|
||||
enc := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(enc, number)
|
||||
return enc
|
||||
}
|
||||
|
||||
// headerKey = headerPrefix + num (uint64 big endian) + hash
|
||||
func headerKey(number uint64, hash common.Hash) []byte {
|
||||
return append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
||||
}
|
||||
|
||||
// headerTDKey = headerPrefix + num (uint64 big endian) + hash + headerTDSuffix
|
||||
func headerTDKey(number uint64, hash common.Hash) []byte {
|
||||
return append(headerKey(number, hash), headerTDSuffix...)
|
||||
}
|
||||
|
||||
// headerHashKey = headerPrefix + num (uint64 big endian) + headerHashSuffix
|
||||
func headerHashKey(number uint64) []byte {
|
||||
return append(append(headerPrefix, encodeBlockNumber(number)...), headerHashSuffix...)
|
||||
}
|
||||
|
||||
// headerNumberKey = headerNumberPrefix + hash
|
||||
func headerNumberKey(hash common.Hash) []byte {
|
||||
return append(headerNumberPrefix, hash.Bytes()...)
|
||||
}
|
||||
|
||||
// blockBodyKey = blockBodyPrefix + num (uint64 big endian) + hash
|
||||
func blockBodyKey(number uint64, hash common.Hash) []byte {
|
||||
return append(append(blockBodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
||||
}
|
||||
|
||||
// blockReceiptsKey = blockReceiptsPrefix + num (uint64 big endian) + hash
|
||||
func blockReceiptsKey(number uint64, hash common.Hash) []byte {
|
||||
return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
|
||||
}
|
||||
|
||||
// txLookupKey = txLookupPrefix + hash
|
||||
func txLookupKey(hash common.Hash) []byte {
|
||||
return append(txLookupPrefix, hash.Bytes()...)
|
||||
}
|
||||
|
||||
// bloomBitsKey = bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash
|
||||
func bloomBitsKey(bit uint, section uint64, hash common.Hash) []byte {
|
||||
key := append(append(bloomBitsPrefix, make([]byte, 10)...), hash.Bytes()...)
|
||||
|
||||
binary.BigEndian.PutUint16(key[1:], uint16(bit))
|
||||
binary.BigEndian.PutUint64(key[3:], section)
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
// preimageKey = preimagePrefix + hash
|
||||
func preimageKey(hash common.Hash) []byte {
|
||||
return append(preimagePrefix, hash.Bytes()...)
|
||||
}
|
||||
|
||||
// configKey = configPrefix + hash
|
||||
func configKey(hash common.Hash) []byte {
|
||||
return append(configPrefix, hash.Bytes()...)
|
||||
}
|
||||
+1
-4
@@ -26,7 +26,7 @@ import (
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// Trie cache generation limit after which to evic trie nodes from memory.
|
||||
// Trie cache generation limit after which to evict trie nodes from memory.
|
||||
var MaxTrieCacheGen = uint16(120)
|
||||
|
||||
const (
|
||||
@@ -151,9 +151,6 @@ func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, erro
|
||||
return cached.(int), nil
|
||||
}
|
||||
code, err := db.ContractCode(addrHash, codeHash)
|
||||
if err == nil {
|
||||
db.codeSizeCache.Add(codeHash, len(code))
|
||||
}
|
||||
return len(code), err
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ func (self *StateDB) RawDump() Dump {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
obj := newObject(nil, common.BytesToAddress(addr), data, nil)
|
||||
obj := newObject(nil, common.BytesToAddress(addr), data)
|
||||
account := DumpAccount{
|
||||
Balance: data.Balance.String(),
|
||||
Nonce: data.Nonce,
|
||||
|
||||
+115
-21
@@ -22,11 +22,67 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// journalEntry is a modification entry in the state change journal that can be
|
||||
// reverted on demand.
|
||||
type journalEntry interface {
|
||||
undo(*StateDB)
|
||||
// revert undoes the changes introduced by this journal entry.
|
||||
revert(*StateDB)
|
||||
|
||||
// dirtied returns the Ethereum address modified by this journal entry.
|
||||
dirtied() *common.Address
|
||||
}
|
||||
|
||||
type journal []journalEntry
|
||||
// journal contains the list of state modifications applied since the last state
|
||||
// commit. These are tracked to be able to be reverted in case of an execution
|
||||
// exception or revertal request.
|
||||
type journal struct {
|
||||
entries []journalEntry // Current changes tracked by the journal
|
||||
dirties map[common.Address]int // Dirty accounts and the number of changes
|
||||
}
|
||||
|
||||
// newJournal create a new initialized journal.
|
||||
func newJournal() *journal {
|
||||
return &journal{
|
||||
dirties: make(map[common.Address]int),
|
||||
}
|
||||
}
|
||||
|
||||
// append inserts a new modification entry to the end of the change journal.
|
||||
func (j *journal) append(entry journalEntry) {
|
||||
j.entries = append(j.entries, entry)
|
||||
if addr := entry.dirtied(); addr != nil {
|
||||
j.dirties[*addr]++
|
||||
}
|
||||
}
|
||||
|
||||
// revert undoes a batch of journalled modifications along with any reverted
|
||||
// dirty handling too.
|
||||
func (j *journal) revert(statedb *StateDB, snapshot int) {
|
||||
for i := len(j.entries) - 1; i >= snapshot; i-- {
|
||||
// Undo the changes made by the operation
|
||||
j.entries[i].revert(statedb)
|
||||
|
||||
// Drop any dirty tracking induced by the change
|
||||
if addr := j.entries[i].dirtied(); addr != nil {
|
||||
if j.dirties[*addr]--; j.dirties[*addr] == 0 {
|
||||
delete(j.dirties, *addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
j.entries = j.entries[:snapshot]
|
||||
}
|
||||
|
||||
// dirty explicitly sets an address to dirty, even if the change entries would
|
||||
// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD
|
||||
// precompile consensus exception.
|
||||
func (j *journal) dirty(addr common.Address) {
|
||||
j.dirties[addr]++
|
||||
}
|
||||
|
||||
// length returns the current number of entries in the journal.
|
||||
func (j *journal) length() int {
|
||||
return len(j.entries)
|
||||
}
|
||||
|
||||
type (
|
||||
// Changes to the account trie.
|
||||
@@ -77,16 +133,24 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func (ch createObjectChange) undo(s *StateDB) {
|
||||
func (ch createObjectChange) revert(s *StateDB) {
|
||||
delete(s.stateObjects, *ch.account)
|
||||
delete(s.stateObjectsDirty, *ch.account)
|
||||
}
|
||||
|
||||
func (ch resetObjectChange) undo(s *StateDB) {
|
||||
func (ch createObjectChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch resetObjectChange) revert(s *StateDB) {
|
||||
s.setStateObject(ch.prev)
|
||||
}
|
||||
|
||||
func (ch suicideChange) undo(s *StateDB) {
|
||||
func (ch resetObjectChange) dirtied() *common.Address {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch suicideChange) revert(s *StateDB) {
|
||||
obj := s.getStateObject(*ch.account)
|
||||
if obj != nil {
|
||||
obj.suicided = ch.prev
|
||||
@@ -94,38 +158,60 @@ func (ch suicideChange) undo(s *StateDB) {
|
||||
}
|
||||
}
|
||||
|
||||
var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
||||
|
||||
func (ch touchChange) undo(s *StateDB) {
|
||||
if !ch.prev && *ch.account != ripemd {
|
||||
s.getStateObject(*ch.account).touched = ch.prev
|
||||
if !ch.prevDirty {
|
||||
delete(s.stateObjectsDirty, *ch.account)
|
||||
}
|
||||
}
|
||||
func (ch suicideChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch balanceChange) undo(s *StateDB) {
|
||||
var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
|
||||
|
||||
func (ch touchChange) revert(s *StateDB) {
|
||||
}
|
||||
|
||||
func (ch touchChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch balanceChange) revert(s *StateDB) {
|
||||
s.getStateObject(*ch.account).setBalance(ch.prev)
|
||||
}
|
||||
|
||||
func (ch nonceChange) undo(s *StateDB) {
|
||||
func (ch balanceChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch nonceChange) revert(s *StateDB) {
|
||||
s.getStateObject(*ch.account).setNonce(ch.prev)
|
||||
}
|
||||
|
||||
func (ch codeChange) undo(s *StateDB) {
|
||||
func (ch nonceChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch codeChange) revert(s *StateDB) {
|
||||
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
|
||||
}
|
||||
|
||||
func (ch storageChange) undo(s *StateDB) {
|
||||
func (ch codeChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch storageChange) revert(s *StateDB) {
|
||||
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
|
||||
}
|
||||
|
||||
func (ch refundChange) undo(s *StateDB) {
|
||||
func (ch storageChange) dirtied() *common.Address {
|
||||
return ch.account
|
||||
}
|
||||
|
||||
func (ch refundChange) revert(s *StateDB) {
|
||||
s.refund = ch.prev
|
||||
}
|
||||
|
||||
func (ch addLogChange) undo(s *StateDB) {
|
||||
func (ch refundChange) dirtied() *common.Address {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch addLogChange) revert(s *StateDB) {
|
||||
logs := s.logs[ch.txhash]
|
||||
if len(logs) == 1 {
|
||||
delete(s.logs, ch.txhash)
|
||||
@@ -135,6 +221,14 @@ func (ch addLogChange) undo(s *StateDB) {
|
||||
s.logSize--
|
||||
}
|
||||
|
||||
func (ch addPreimageChange) undo(s *StateDB) {
|
||||
func (ch addLogChange) dirtied() *common.Address {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ch addPreimageChange) revert(s *StateDB) {
|
||||
delete(s.preimages, ch.hash)
|
||||
}
|
||||
|
||||
func (ch addPreimageChange) dirtied() *common.Address {
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-2
@@ -26,8 +26,7 @@ import (
|
||||
var addr = common.BytesToAddress([]byte("test"))
|
||||
|
||||
func create() (*ManagedState, *account) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := New(common.Hash{}, NewDatabase(db))
|
||||
statedb, _ := New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase()))
|
||||
ms := ManageState(statedb)
|
||||
ms.StateDB.SetNonce(addr, 100)
|
||||
ms.accounts[addr] = newAccount(ms.StateDB.getStateObject(addr))
|
||||
|
||||
+15
-43
@@ -85,9 +85,7 @@ type stateObject struct {
|
||||
// during the "update" phase of the state transition.
|
||||
dirtyCode bool // true if the code was updated
|
||||
suicided bool
|
||||
touched bool
|
||||
deleted bool
|
||||
onDirty func(addr common.Address) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
// empty returns whether the account is considered empty.
|
||||
@@ -105,7 +103,7 @@ type Account struct {
|
||||
}
|
||||
|
||||
// newObject creates a state object.
|
||||
func newObject(db *StateDB, address common.Address, data Account, onDirty func(addr common.Address)) *stateObject {
|
||||
func newObject(db *StateDB, address common.Address, data Account) *stateObject {
|
||||
if data.Balance == nil {
|
||||
data.Balance = new(big.Int)
|
||||
}
|
||||
@@ -119,7 +117,6 @@ func newObject(db *StateDB, address common.Address, data Account, onDirty func(a
|
||||
data: data,
|
||||
cachedStorage: make(Storage),
|
||||
dirtyStorage: make(Storage),
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,23 +134,17 @@ func (self *stateObject) setError(err error) {
|
||||
|
||||
func (self *stateObject) markSuicided() {
|
||||
self.suicided = true
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Address())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *stateObject) touch() {
|
||||
c.db.journal = append(c.db.journal, touchChange{
|
||||
account: &c.address,
|
||||
prev: c.touched,
|
||||
prevDirty: c.onDirty == nil,
|
||||
c.db.journal.append(touchChange{
|
||||
account: &c.address,
|
||||
})
|
||||
if c.onDirty != nil {
|
||||
c.onDirty(c.Address())
|
||||
c.onDirty = nil
|
||||
if c.address == ripemd {
|
||||
// Explicitly put it in the dirty-cache, which is otherwise generated from
|
||||
// flattened journals.
|
||||
c.db.journal.dirty(c.address)
|
||||
}
|
||||
c.touched = true
|
||||
}
|
||||
|
||||
func (c *stateObject) getTrie(db Database) Trie {
|
||||
@@ -187,15 +178,13 @@ func (self *stateObject) GetState(db Database, key common.Hash) common.Hash {
|
||||
}
|
||||
value.SetBytes(content)
|
||||
}
|
||||
if (value != common.Hash{}) {
|
||||
self.cachedStorage[key] = value
|
||||
}
|
||||
self.cachedStorage[key] = value
|
||||
return value
|
||||
}
|
||||
|
||||
// SetState updates a value in account storage.
|
||||
func (self *stateObject) SetState(db Database, key, value common.Hash) {
|
||||
self.db.journal = append(self.db.journal, storageChange{
|
||||
self.db.journal.append(storageChange{
|
||||
account: &self.address,
|
||||
key: key,
|
||||
prevalue: self.GetState(db, key),
|
||||
@@ -206,11 +195,6 @@ func (self *stateObject) SetState(db Database, key, value common.Hash) {
|
||||
func (self *stateObject) setState(key, value common.Hash) {
|
||||
self.cachedStorage[key] = value
|
||||
self.dirtyStorage[key] = value
|
||||
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Address())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
// updateTrie writes cached storage modifications into the object's storage trie.
|
||||
@@ -235,7 +219,7 @@ func (self *stateObject) updateRoot(db Database) {
|
||||
self.data.Root = self.trie.Hash()
|
||||
}
|
||||
|
||||
// CommitTrie the storage trie of the object to dwb.
|
||||
// CommitTrie the storage trie of the object to db.
|
||||
// This updates the trie root.
|
||||
func (self *stateObject) CommitTrie(db Database) error {
|
||||
self.updateTrie(db)
|
||||
@@ -274,7 +258,7 @@ func (c *stateObject) SubBalance(amount *big.Int) {
|
||||
}
|
||||
|
||||
func (self *stateObject) SetBalance(amount *big.Int) {
|
||||
self.db.journal = append(self.db.journal, balanceChange{
|
||||
self.db.journal.append(balanceChange{
|
||||
account: &self.address,
|
||||
prev: new(big.Int).Set(self.data.Balance),
|
||||
})
|
||||
@@ -283,17 +267,13 @@ func (self *stateObject) SetBalance(amount *big.Int) {
|
||||
|
||||
func (self *stateObject) setBalance(amount *big.Int) {
|
||||
self.data.Balance = amount
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Address())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Return the gas back to the origin. Used by the Virtual machine or Closures
|
||||
func (c *stateObject) ReturnGas(gas *big.Int) {}
|
||||
|
||||
func (self *stateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *stateObject {
|
||||
stateObject := newObject(db, self.address, self.data, onDirty)
|
||||
func (self *stateObject) deepCopy(db *StateDB) *stateObject {
|
||||
stateObject := newObject(db, self.address, self.data)
|
||||
if self.trie != nil {
|
||||
stateObject.trie = db.db.CopyTrie(self.trie)
|
||||
}
|
||||
@@ -333,7 +313,7 @@ func (self *stateObject) Code(db Database) []byte {
|
||||
|
||||
func (self *stateObject) SetCode(codeHash common.Hash, code []byte) {
|
||||
prevcode := self.Code(self.db.db)
|
||||
self.db.journal = append(self.db.journal, codeChange{
|
||||
self.db.journal.append(codeChange{
|
||||
account: &self.address,
|
||||
prevhash: self.CodeHash(),
|
||||
prevcode: prevcode,
|
||||
@@ -345,14 +325,10 @@ func (self *stateObject) setCode(codeHash common.Hash, code []byte) {
|
||||
self.code = code
|
||||
self.data.CodeHash = codeHash[:]
|
||||
self.dirtyCode = true
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Address())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *stateObject) SetNonce(nonce uint64) {
|
||||
self.db.journal = append(self.db.journal, nonceChange{
|
||||
self.db.journal.append(nonceChange{
|
||||
account: &self.address,
|
||||
prev: self.data.Nonce,
|
||||
})
|
||||
@@ -361,10 +337,6 @@ func (self *stateObject) SetNonce(nonce uint64) {
|
||||
|
||||
func (self *stateObject) setNonce(nonce uint64) {
|
||||
self.data.Nonce = nonce
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Address())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *stateObject) CodeHash() []byte {
|
||||
|
||||
+3
-4
@@ -87,7 +87,7 @@ func (s *StateSuite) TestDump(c *checker.C) {
|
||||
}
|
||||
|
||||
func (s *StateSuite) SetUpTest(c *checker.C) {
|
||||
s.db, _ = ethdb.NewMemDatabase()
|
||||
s.db = ethdb.NewMemDatabase()
|
||||
s.state, _ = New(common.Hash{}, NewDatabase(s.db))
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func (s *StateSuite) TestNull(c *checker.C) {
|
||||
s.state.SetState(address, common.Hash{}, value)
|
||||
s.state.Commit(false)
|
||||
value = s.state.GetState(address, common.Hash{})
|
||||
if !common.EmptyHash(value) {
|
||||
if value != (common.Hash{}) {
|
||||
c.Errorf("expected empty hash. got %x", value)
|
||||
}
|
||||
}
|
||||
@@ -133,8 +133,7 @@ func (s *StateSuite) TestSnapshotEmpty(c *checker.C) {
|
||||
// use testing instead of checker because checker does not support
|
||||
// printing/logging in tests (-check.vv does not work)
|
||||
func TestSnapshot2(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
state, _ := New(common.Hash{}, NewDatabase(db))
|
||||
state, _ := New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase()))
|
||||
|
||||
stateobjaddr0 := toAddr([]byte("so0"))
|
||||
stateobjaddr1 := toAddr([]byte("so1"))
|
||||
|
||||
+71
-68
@@ -76,14 +76,14 @@ type StateDB struct {
|
||||
|
||||
// Journal of state modifications. This is the backbone of
|
||||
// Snapshot and RevertToSnapshot.
|
||||
journal journal
|
||||
journal *journal
|
||||
validRevisions []revision
|
||||
nextRevisionId int
|
||||
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// Create a new state from a given trie
|
||||
// Create a new state from a given trie.
|
||||
func New(root common.Hash, db Database) (*StateDB, error) {
|
||||
tr, err := db.OpenTrie(root)
|
||||
if err != nil {
|
||||
@@ -96,6 +96,7 @@ func New(root common.Hash, db Database) (*StateDB, error) {
|
||||
stateObjectsDirty: make(map[common.Address]struct{}),
|
||||
logs: make(map[common.Hash][]*types.Log),
|
||||
preimages: make(map[common.Hash][]byte),
|
||||
journal: newJournal(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -110,7 +111,7 @@ func (self *StateDB) Error() error {
|
||||
return self.dbErr
|
||||
}
|
||||
|
||||
// Reset clears out all emphemeral state objects from the state db, but keeps
|
||||
// Reset clears out all ephemeral state objects from the state db, but keeps
|
||||
// the underlying state trie to avoid reloading data for the next operations.
|
||||
func (self *StateDB) Reset(root common.Hash) error {
|
||||
tr, err := self.db.OpenTrie(root)
|
||||
@@ -131,7 +132,7 @@ func (self *StateDB) Reset(root common.Hash) error {
|
||||
}
|
||||
|
||||
func (self *StateDB) AddLog(log *types.Log) {
|
||||
self.journal = append(self.journal, addLogChange{txhash: self.thash})
|
||||
self.journal.append(addLogChange{txhash: self.thash})
|
||||
|
||||
log.TxHash = self.thash
|
||||
log.BlockHash = self.bhash
|
||||
@@ -156,7 +157,7 @@ func (self *StateDB) Logs() []*types.Log {
|
||||
// AddPreimage records a SHA3 preimage seen by the VM.
|
||||
func (self *StateDB) AddPreimage(hash common.Hash, preimage []byte) {
|
||||
if _, ok := self.preimages[hash]; !ok {
|
||||
self.journal = append(self.journal, addPreimageChange{hash: hash})
|
||||
self.journal.append(addPreimageChange{hash: hash})
|
||||
pi := make([]byte, len(preimage))
|
||||
copy(pi, preimage)
|
||||
self.preimages[hash] = pi
|
||||
@@ -169,7 +170,7 @@ func (self *StateDB) Preimages() map[common.Hash][]byte {
|
||||
}
|
||||
|
||||
func (self *StateDB) AddRefund(gas uint64) {
|
||||
self.journal = append(self.journal, refundChange{prev: self.refund})
|
||||
self.journal.append(refundChange{prev: self.refund})
|
||||
self.refund += gas
|
||||
}
|
||||
|
||||
@@ -235,10 +236,10 @@ func (self *StateDB) GetCodeHash(addr common.Address) common.Hash {
|
||||
return common.BytesToHash(stateObject.CodeHash())
|
||||
}
|
||||
|
||||
func (self *StateDB) GetState(a common.Address, b common.Hash) common.Hash {
|
||||
stateObject := self.getStateObject(a)
|
||||
func (self *StateDB) GetState(addr common.Address, bhash common.Hash) common.Hash {
|
||||
stateObject := self.getStateObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.GetState(self.db, b)
|
||||
return stateObject.GetState(self.db, bhash)
|
||||
}
|
||||
return common.Hash{}
|
||||
}
|
||||
@@ -250,12 +251,12 @@ func (self *StateDB) Database() Database {
|
||||
|
||||
// StorageTrie returns the storage trie of an account.
|
||||
// The return value is a copy and is nil for non-existent accounts.
|
||||
func (self *StateDB) StorageTrie(a common.Address) Trie {
|
||||
stateObject := self.getStateObject(a)
|
||||
func (self *StateDB) StorageTrie(addr common.Address) Trie {
|
||||
stateObject := self.getStateObject(addr)
|
||||
if stateObject == nil {
|
||||
return nil
|
||||
}
|
||||
cpy := stateObject.deepCopy(self, nil)
|
||||
cpy := stateObject.deepCopy(self)
|
||||
return cpy.updateTrie(self.db)
|
||||
}
|
||||
|
||||
@@ -271,7 +272,7 @@ func (self *StateDB) HasSuicided(addr common.Address) bool {
|
||||
* SETTERS
|
||||
*/
|
||||
|
||||
// AddBalance adds amount to the account associated with addr
|
||||
// AddBalance adds amount to the account associated with addr.
|
||||
func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) {
|
||||
stateObject := self.GetOrNewStateObject(addr)
|
||||
if stateObject != nil {
|
||||
@@ -279,7 +280,7 @@ func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) {
|
||||
}
|
||||
}
|
||||
|
||||
// SubBalance subtracts amount from the account associated with addr
|
||||
// SubBalance subtracts amount from the account associated with addr.
|
||||
func (self *StateDB) SubBalance(addr common.Address, amount *big.Int) {
|
||||
stateObject := self.GetOrNewStateObject(addr)
|
||||
if stateObject != nil {
|
||||
@@ -308,7 +309,7 @@ func (self *StateDB) SetCode(addr common.Address, code []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *StateDB) SetState(addr common.Address, key common.Hash, value common.Hash) {
|
||||
func (self *StateDB) SetState(addr common.Address, key, value common.Hash) {
|
||||
stateObject := self.GetOrNewStateObject(addr)
|
||||
if stateObject != nil {
|
||||
stateObject.SetState(self.db, key, value)
|
||||
@@ -325,7 +326,7 @@ func (self *StateDB) Suicide(addr common.Address) bool {
|
||||
if stateObject == nil {
|
||||
return false
|
||||
}
|
||||
self.journal = append(self.journal, suicideChange{
|
||||
self.journal.append(suicideChange{
|
||||
account: &addr,
|
||||
prev: stateObject.suicided,
|
||||
prevbalance: new(big.Int).Set(stateObject.Balance()),
|
||||
@@ -337,7 +338,7 @@ func (self *StateDB) Suicide(addr common.Address) bool {
|
||||
}
|
||||
|
||||
//
|
||||
// Setting, updating & deleting state object methods
|
||||
// Setting, updating & deleting state object methods.
|
||||
//
|
||||
|
||||
// updateStateObject writes the given object to the trie.
|
||||
@@ -357,7 +358,7 @@ func (self *StateDB) deleteStateObject(stateObject *stateObject) {
|
||||
self.setError(self.trie.TryDelete(addr[:]))
|
||||
}
|
||||
|
||||
// Retrieve a state object given my the address. Returns nil if not found.
|
||||
// Retrieve a state object given by the address. Returns nil if not found.
|
||||
func (self *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.stateObjects[addr]; obj != nil {
|
||||
@@ -379,7 +380,7 @@ func (self *StateDB) getStateObject(addr common.Address) (stateObject *stateObje
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newObject(self, addr, data, self.MarkStateObjectDirty)
|
||||
obj := newObject(self, addr, data)
|
||||
self.setStateObject(obj)
|
||||
return obj
|
||||
}
|
||||
@@ -388,7 +389,7 @@ func (self *StateDB) setStateObject(object *stateObject) {
|
||||
self.stateObjects[object.Address()] = object
|
||||
}
|
||||
|
||||
// Retrieve a state object or create a new state object if nil
|
||||
// Retrieve a state object or create a new state object if nil.
|
||||
func (self *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
|
||||
stateObject := self.getStateObject(addr)
|
||||
if stateObject == nil || stateObject.deleted {
|
||||
@@ -397,22 +398,16 @@ func (self *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
|
||||
return stateObject
|
||||
}
|
||||
|
||||
// MarkStateObjectDirty adds the specified object to the dirty map to avoid costly
|
||||
// state object cache iteration to find a handful of modified ones.
|
||||
func (self *StateDB) MarkStateObjectDirty(addr common.Address) {
|
||||
self.stateObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
|
||||
// createObject creates a new state object. If there is an existing account with
|
||||
// the given address, it is overwritten and returned as the second return value.
|
||||
func (self *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
|
||||
prev = self.getStateObject(addr)
|
||||
newobj = newObject(self, addr, Account{}, self.MarkStateObjectDirty)
|
||||
newobj = newObject(self, addr, Account{})
|
||||
newobj.setNonce(0) // sets the object to dirty
|
||||
if prev == nil {
|
||||
self.journal = append(self.journal, createObjectChange{account: &addr})
|
||||
self.journal.append(createObjectChange{account: &addr})
|
||||
} else {
|
||||
self.journal = append(self.journal, resetObjectChange{prev: prev})
|
||||
self.journal.append(resetObjectChange{prev: prev})
|
||||
}
|
||||
self.setStateObject(newobj)
|
||||
return newobj, prev
|
||||
@@ -466,21 +461,41 @@ func (self *StateDB) Copy() *StateDB {
|
||||
state := &StateDB{
|
||||
db: self.db,
|
||||
trie: self.db.CopyTrie(self.trie),
|
||||
stateObjects: make(map[common.Address]*stateObject, len(self.stateObjectsDirty)),
|
||||
stateObjectsDirty: make(map[common.Address]struct{}, len(self.stateObjectsDirty)),
|
||||
stateObjects: make(map[common.Address]*stateObject, len(self.journal.dirties)),
|
||||
stateObjectsDirty: make(map[common.Address]struct{}, len(self.journal.dirties)),
|
||||
refund: self.refund,
|
||||
logs: make(map[common.Hash][]*types.Log, len(self.logs)),
|
||||
logSize: self.logSize,
|
||||
preimages: make(map[common.Hash][]byte),
|
||||
journal: newJournal(),
|
||||
}
|
||||
// Copy the dirty states, logs, and preimages
|
||||
for addr := range self.journal.dirties {
|
||||
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527),
|
||||
// and in the Finalise-method, there is a case where an object is in the journal but not
|
||||
// in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for
|
||||
// nil
|
||||
if object, exist := self.stateObjects[addr]; exist {
|
||||
state.stateObjects[addr] = object.deepCopy(state)
|
||||
state.stateObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
}
|
||||
// Above, we don't copy the actual journal. This means that if the copy is copied, the
|
||||
// loop above will be a no-op, since the copy's journal is empty.
|
||||
// Thus, here we iterate over stateObjects, to enable copies of copies
|
||||
for addr := range self.stateObjectsDirty {
|
||||
state.stateObjects[addr] = self.stateObjects[addr].deepCopy(state, state.MarkStateObjectDirty)
|
||||
state.stateObjectsDirty[addr] = struct{}{}
|
||||
if _, exist := state.stateObjects[addr]; !exist {
|
||||
state.stateObjects[addr] = self.stateObjects[addr].deepCopy(state)
|
||||
state.stateObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
}
|
||||
for hash, logs := range self.logs {
|
||||
state.logs[hash] = make([]*types.Log, len(logs))
|
||||
copy(state.logs[hash], logs)
|
||||
cpy := make([]*types.Log, len(logs))
|
||||
for i, l := range logs {
|
||||
cpy[i] = new(types.Log)
|
||||
*cpy[i] = *l
|
||||
}
|
||||
state.logs[hash] = cpy
|
||||
}
|
||||
for hash, preimage := range self.preimages {
|
||||
state.preimages[hash] = preimage
|
||||
@@ -492,7 +507,7 @@ func (self *StateDB) Copy() *StateDB {
|
||||
func (self *StateDB) Snapshot() int {
|
||||
id := self.nextRevisionId
|
||||
self.nextRevisionId++
|
||||
self.validRevisions = append(self.validRevisions, revision{id, len(self.journal)})
|
||||
self.validRevisions = append(self.validRevisions, revision{id, self.journal.length()})
|
||||
return id
|
||||
}
|
||||
|
||||
@@ -507,13 +522,8 @@ func (self *StateDB) RevertToSnapshot(revid int) {
|
||||
}
|
||||
snapshot := self.validRevisions[idx].journalIndex
|
||||
|
||||
// Replay the journal to undo changes.
|
||||
for i := len(self.journal) - 1; i >= snapshot; i-- {
|
||||
self.journal[i].undo(self)
|
||||
}
|
||||
self.journal = self.journal[:snapshot]
|
||||
|
||||
// Remove invalidated snapshots from the stack.
|
||||
// Replay the journal to undo changes and remove invalidated snapshots
|
||||
self.journal.revert(self, snapshot)
|
||||
self.validRevisions = self.validRevisions[:idx]
|
||||
}
|
||||
|
||||
@@ -525,14 +535,25 @@ func (self *StateDB) GetRefund() uint64 {
|
||||
// Finalise finalises the state by removing the self destructed objects
|
||||
// and clears the journal as well as the refunds.
|
||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||
for addr := range s.stateObjectsDirty {
|
||||
stateObject := s.stateObjects[addr]
|
||||
for addr := range s.journal.dirties {
|
||||
stateObject, exist := s.stateObjects[addr]
|
||||
if !exist {
|
||||
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
|
||||
// That tx goes out of gas, and although the notion of 'touched' does not exist there, the
|
||||
// touch-event will still be recorded in the journal. Since ripeMD is a special snowflake,
|
||||
// it will persist in the journal even though the journal is reverted. In this special circumstance,
|
||||
// it may exist in `s.journal.dirties` but not in `s.stateObjects`.
|
||||
// Thus, we can safely ignore it here
|
||||
continue
|
||||
}
|
||||
|
||||
if stateObject.suicided || (deleteEmptyObjects && stateObject.empty()) {
|
||||
s.deleteStateObject(stateObject)
|
||||
} else {
|
||||
stateObject.updateRoot(s.db)
|
||||
s.updateStateObject(stateObject)
|
||||
}
|
||||
s.stateObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
// Invalidate journal because reverting across transactions is not allowed.
|
||||
s.clearJournalAndRefund()
|
||||
@@ -554,29 +575,8 @@ func (self *StateDB) Prepare(thash, bhash common.Hash, ti int) {
|
||||
self.txIndex = ti
|
||||
}
|
||||
|
||||
// DeleteSuicides flags the suicided objects for deletion so that it
|
||||
// won't be referenced again when called / queried up on.
|
||||
//
|
||||
// DeleteSuicides should not be used for consensus related updates
|
||||
// under any circumstances.
|
||||
func (s *StateDB) DeleteSuicides() {
|
||||
// Reset refund so that any used-gas calculations can use this method.
|
||||
s.clearJournalAndRefund()
|
||||
|
||||
for addr := range s.stateObjectsDirty {
|
||||
stateObject := s.stateObjects[addr]
|
||||
|
||||
// If the object has been removed by a suicide
|
||||
// flag the object as deleted.
|
||||
if stateObject.suicided {
|
||||
stateObject.deleted = true
|
||||
}
|
||||
delete(s.stateObjectsDirty, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StateDB) clearJournalAndRefund() {
|
||||
s.journal = nil
|
||||
s.journal = newJournal()
|
||||
s.validRevisions = s.validRevisions[:0]
|
||||
s.refund = 0
|
||||
}
|
||||
@@ -585,6 +585,9 @@ func (s *StateDB) clearJournalAndRefund() {
|
||||
func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) {
|
||||
defer s.clearJournalAndRefund()
|
||||
|
||||
for addr := range s.journal.dirties {
|
||||
s.stateObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
// Commit objects to the trie.
|
||||
for addr, stateObject := range s.stateObjects {
|
||||
_, isDirty := s.stateObjectsDirty[addr]
|
||||
@@ -596,7 +599,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error)
|
||||
case isDirty:
|
||||
// Write any contract code associated with the state object
|
||||
if stateObject.code != nil && stateObject.dirtyCode {
|
||||
s.db.TrieDB().Insert(common.BytesToHash(stateObject.CodeHash()), stateObject.code)
|
||||
s.db.TrieDB().InsertBlob(common.BytesToHash(stateObject.CodeHash()), stateObject.code)
|
||||
stateObject.dirtyCode = false
|
||||
}
|
||||
// Write any storage changes in the state object to its storage trie.
|
||||
|
||||
+23
-9
@@ -39,7 +39,7 @@ import (
|
||||
// actually committing the state.
|
||||
func TestUpdateLeaks(t *testing.T) {
|
||||
// Create an empty state database
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
db := ethdb.NewMemDatabase()
|
||||
state, _ := New(common.Hash{}, NewDatabase(db))
|
||||
|
||||
// Update it with some accounts
|
||||
@@ -66,8 +66,8 @@ func TestUpdateLeaks(t *testing.T) {
|
||||
// only the one right before the commit.
|
||||
func TestIntermediateLeaks(t *testing.T) {
|
||||
// Create two state databases, one transitioning to the final state, the other final from the beginning
|
||||
transDb, _ := ethdb.NewMemDatabase()
|
||||
finalDb, _ := ethdb.NewMemDatabase()
|
||||
transDb := ethdb.NewMemDatabase()
|
||||
finalDb := ethdb.NewMemDatabase()
|
||||
transState, _ := New(common.Hash{}, NewDatabase(transDb))
|
||||
finalState, _ := New(common.Hash{}, NewDatabase(finalDb))
|
||||
|
||||
@@ -122,8 +122,7 @@ func TestIntermediateLeaks(t *testing.T) {
|
||||
// https://github.com/ethereum/go-ethereum/pull/15549.
|
||||
func TestCopy(t *testing.T) {
|
||||
// Create a random state test to copy and modify "independently"
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
orig, _ := New(common.Hash{}, NewDatabase(db))
|
||||
orig, _ := New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase()))
|
||||
|
||||
for i := byte(0); i < 255; i++ {
|
||||
obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
|
||||
@@ -334,8 +333,7 @@ func (test *snapshotTest) String() string {
|
||||
func (test *snapshotTest) run() bool {
|
||||
// Run all actions and create snapshots.
|
||||
var (
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
state, _ = New(common.Hash{}, NewDatabase(db))
|
||||
state, _ = New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase()))
|
||||
snapshotRevs = make([]int, len(test.snapshots))
|
||||
sindex = 0
|
||||
)
|
||||
@@ -413,11 +411,27 @@ func (s *StateSuite) TestTouchDelete(c *check.C) {
|
||||
|
||||
snapshot := s.state.Snapshot()
|
||||
s.state.AddBalance(common.Address{}, new(big.Int))
|
||||
if len(s.state.stateObjectsDirty) != 1 {
|
||||
|
||||
if len(s.state.journal.dirties) != 1 {
|
||||
c.Fatal("expected one dirty state object")
|
||||
}
|
||||
s.state.RevertToSnapshot(snapshot)
|
||||
if len(s.state.stateObjectsDirty) != 0 {
|
||||
if len(s.state.journal.dirties) != 0 {
|
||||
c.Fatal("expected no dirty state object")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy.
|
||||
// See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512
|
||||
func TestCopyOfCopy(t *testing.T) {
|
||||
sdb, _ := New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase()))
|
||||
addr := common.HexToAddress("aaaa")
|
||||
sdb.SetBalance(addr, big.NewInt(42))
|
||||
|
||||
if got := sdb.Copy().GetBalance(addr).Uint64(); got != 42 {
|
||||
t.Fatalf("1st copy fail, expected 42, got %v", got)
|
||||
}
|
||||
if got := sdb.Copy().Copy().GetBalance(addr).Uint64(); got != 42 {
|
||||
t.Fatalf("2nd copy fail, expected 42, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -25,8 +25,8 @@ import (
|
||||
)
|
||||
|
||||
// NewStateSync create a new state trie download scheduler.
|
||||
func NewStateSync(root common.Hash, database trie.DatabaseReader) *trie.TrieSync {
|
||||
var syncer *trie.TrieSync
|
||||
func NewStateSync(root common.Hash, database trie.DatabaseReader) *trie.Sync {
|
||||
var syncer *trie.Sync
|
||||
callback := func(leaf []byte, parent common.Hash) error {
|
||||
var obj Account
|
||||
if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil {
|
||||
@@ -36,6 +36,6 @@ func NewStateSync(root common.Hash, database trie.DatabaseReader) *trie.TrieSync
|
||||
syncer.AddRawEntry(common.BytesToHash(obj.CodeHash), 64, parent)
|
||||
return nil
|
||||
}
|
||||
syncer = trie.NewTrieSync(root, database, callback)
|
||||
syncer = trie.NewSync(root, database, callback)
|
||||
return syncer
|
||||
}
|
||||
|
||||
+7
-9
@@ -38,8 +38,7 @@ type testAccount struct {
|
||||
// makeTestState create a sample test state to test node-wise reconstruction.
|
||||
func makeTestState() (Database, common.Hash, []*testAccount) {
|
||||
// Create an empty state
|
||||
diskdb, _ := ethdb.NewMemDatabase()
|
||||
db := NewDatabase(diskdb)
|
||||
db := NewDatabase(ethdb.NewMemDatabase())
|
||||
state, _ := New(common.Hash{}, db)
|
||||
|
||||
// Fill it with some arbitrary data
|
||||
@@ -125,8 +124,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")
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
if req := NewStateSync(empty, db).Missing(1); len(req) != 0 {
|
||||
if req := NewStateSync(empty, ethdb.NewMemDatabase()).Missing(1); len(req) != 0 {
|
||||
t.Errorf("content requested for empty state: %v", req)
|
||||
}
|
||||
}
|
||||
@@ -141,7 +139,7 @@ func testIterativeStateSync(t *testing.T, batch int) {
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb, _ := ethdb.NewMemDatabase()
|
||||
dstDb := ethdb.NewMemDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb)
|
||||
|
||||
queue := append([]common.Hash{}, sched.Missing(batch)...)
|
||||
@@ -173,7 +171,7 @@ func TestIterativeDelayedStateSync(t *testing.T) {
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb, _ := ethdb.NewMemDatabase()
|
||||
dstDb := ethdb.NewMemDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb)
|
||||
|
||||
queue := append([]common.Hash{}, sched.Missing(0)...)
|
||||
@@ -210,7 +208,7 @@ func testIterativeRandomStateSync(t *testing.T, batch int) {
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb, _ := ethdb.NewMemDatabase()
|
||||
dstDb := ethdb.NewMemDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb)
|
||||
|
||||
queue := make(map[common.Hash]struct{})
|
||||
@@ -250,7 +248,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) {
|
||||
srcDb, srcRoot, srcAccounts := makeTestState()
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb, _ := ethdb.NewMemDatabase()
|
||||
dstDb := ethdb.NewMemDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb)
|
||||
|
||||
queue := make(map[common.Hash]struct{})
|
||||
@@ -297,7 +295,7 @@ func TestIncompleteStateSync(t *testing.T) {
|
||||
checkTrieConsistency(srcDb.TrieDB().DiskDB().(ethdb.Database), srcRoot)
|
||||
|
||||
// Create a destination state and sync with the scheduler
|
||||
dstDb, _ := ethdb.NewMemDatabase()
|
||||
dstDb := ethdb.NewMemDatabase()
|
||||
sched := NewStateSync(srcRoot, dstDb)
|
||||
|
||||
added := []common.Hash{}
|
||||
|
||||
+2
-2
@@ -61,7 +61,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||
allLogs []*types.Log
|
||||
gp = new(GasPool).AddGas(block.GasLimit())
|
||||
)
|
||||
// Mutate the the block and state according to any hard-fork specs
|
||||
// Mutate the block and state according to any hard-fork specs
|
||||
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||
misc.ApplyDAOHardFork(statedb)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
||||
// and uses the input parameters for its environment. It returns the receipt
|
||||
// for the transaction, gas used and an error if the transaction failed,
|
||||
// indicating the block was invalid.
|
||||
func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, uint64, error) {
|
||||
func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, uint64, error) {
|
||||
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
||||
+19
-45
@@ -35,7 +35,7 @@ var (
|
||||
The State Transitioning Model
|
||||
|
||||
A state transition is a change made when a transaction is applied to the current world state
|
||||
The state transitioning model does all all the necessary work to work out a valid new state root.
|
||||
The state transitioning model does all the necessary work to work out a valid new state root.
|
||||
|
||||
1) Nonce handling
|
||||
2) Pre pay gas
|
||||
@@ -132,28 +132,12 @@ func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool,
|
||||
return NewStateTransition(evm, msg, gp).TransitionDb()
|
||||
}
|
||||
|
||||
func (st *StateTransition) from() vm.AccountRef {
|
||||
f := st.msg.From()
|
||||
if !st.state.Exist(f) {
|
||||
st.state.CreateAccount(f)
|
||||
// to returns the recipient of the message.
|
||||
func (st *StateTransition) to() common.Address {
|
||||
if st.msg == nil || st.msg.To() == nil /* contract creation */ {
|
||||
return common.Address{}
|
||||
}
|
||||
return vm.AccountRef(f)
|
||||
}
|
||||
|
||||
func (st *StateTransition) to() vm.AccountRef {
|
||||
if st.msg == nil {
|
||||
return vm.AccountRef{}
|
||||
}
|
||||
to := st.msg.To()
|
||||
if to == nil {
|
||||
return vm.AccountRef{} // contract creation
|
||||
}
|
||||
|
||||
reference := vm.AccountRef(*to)
|
||||
if !st.state.Exist(*to) {
|
||||
st.state.CreateAccount(*to)
|
||||
}
|
||||
return reference
|
||||
return *st.msg.To()
|
||||
}
|
||||
|
||||
func (st *StateTransition) useGas(amount uint64) error {
|
||||
@@ -166,12 +150,8 @@ func (st *StateTransition) useGas(amount uint64) error {
|
||||
}
|
||||
|
||||
func (st *StateTransition) buyGas() error {
|
||||
var (
|
||||
state = st.state
|
||||
sender = st.from()
|
||||
)
|
||||
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
|
||||
if state.GetBalance(sender.Address()).Cmp(mgval) < 0 {
|
||||
if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
|
||||
return errInsufficientBalanceForGas
|
||||
}
|
||||
if err := st.gp.SubGas(st.msg.Gas()); err != nil {
|
||||
@@ -180,20 +160,17 @@ func (st *StateTransition) buyGas() error {
|
||||
st.gas += st.msg.Gas()
|
||||
|
||||
st.initialGas = st.msg.Gas()
|
||||
state.SubBalance(sender.Address(), mgval)
|
||||
st.state.SubBalance(st.msg.From(), mgval)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *StateTransition) preCheck() error {
|
||||
msg := st.msg
|
||||
sender := st.from()
|
||||
|
||||
// Make sure this transaction's nonce is correct
|
||||
if msg.CheckNonce() {
|
||||
nonce := st.state.GetNonce(sender.Address())
|
||||
if nonce < msg.Nonce() {
|
||||
// Make sure this transaction's nonce is correct.
|
||||
if st.msg.CheckNonce() {
|
||||
nonce := st.state.GetNonce(st.msg.From())
|
||||
if nonce < st.msg.Nonce() {
|
||||
return ErrNonceTooHigh
|
||||
} else if nonce > msg.Nonce() {
|
||||
} else if nonce > st.msg.Nonce() {
|
||||
return ErrNonceTooLow
|
||||
}
|
||||
}
|
||||
@@ -201,15 +178,14 @@ func (st *StateTransition) preCheck() error {
|
||||
}
|
||||
|
||||
// TransitionDb will transition the state by applying the current message and
|
||||
// returning the result including the the used gas. It returns an error if it
|
||||
// failed. An error indicates a consensus issue.
|
||||
// returning the result including the used gas. It returns an error if failed.
|
||||
// An error indicates a consensus issue.
|
||||
func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
|
||||
if err = st.preCheck(); err != nil {
|
||||
return
|
||||
}
|
||||
msg := st.msg
|
||||
sender := st.from() // err checked in preCheck
|
||||
|
||||
sender := vm.AccountRef(msg.From())
|
||||
homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
|
||||
contractCreation := msg.To() == nil
|
||||
|
||||
@@ -233,8 +209,8 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
|
||||
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
|
||||
} else {
|
||||
// Increment the nonce for the next transaction
|
||||
st.state.SetNonce(sender.Address(), st.state.GetNonce(sender.Address())+1)
|
||||
ret, st.gas, vmerr = evm.Call(sender, st.to().Address(), st.data, st.gas, st.value)
|
||||
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
|
||||
ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)
|
||||
}
|
||||
if vmerr != nil {
|
||||
log.Debug("VM returned with error", "err", vmerr)
|
||||
@@ -260,10 +236,8 @@ func (st *StateTransition) refundGas() {
|
||||
st.gas += refund
|
||||
|
||||
// Return ETH for remaining gas, exchanged at the original rate.
|
||||
sender := st.from()
|
||||
|
||||
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
|
||||
st.state.AddBalance(sender.Address(), remaining)
|
||||
st.state.AddBalance(st.msg.From(), remaining)
|
||||
|
||||
// Also return remaining gas to the block gas counter so it is
|
||||
// available for the next transaction.
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// senderCacher is a concurrent transaction sender recoverer anc cacher.
|
||||
var senderCacher = newTxSenderCacher(runtime.NumCPU())
|
||||
|
||||
// txSenderCacherRequest is a request for recovering transaction senders with a
|
||||
// specific signature scheme and caching it into the transactions themselves.
|
||||
//
|
||||
// The inc field defines the number of transactions to skip after each recovery,
|
||||
// which is used to feed the same underlying input array to different threads but
|
||||
// ensure they process the early transactions fast.
|
||||
type txSenderCacherRequest struct {
|
||||
signer types.Signer
|
||||
txs []*types.Transaction
|
||||
inc int
|
||||
}
|
||||
|
||||
// txSenderCacher is a helper structure to concurrently ecrecover transaction
|
||||
// senders from digital signatures on background threads.
|
||||
type txSenderCacher struct {
|
||||
threads int
|
||||
tasks chan *txSenderCacherRequest
|
||||
}
|
||||
|
||||
// newTxSenderCacher creates a new transaction sender background cacher and starts
|
||||
// as many processing goroutines as allowed by the GOMAXPROCS on construction.
|
||||
func newTxSenderCacher(threads int) *txSenderCacher {
|
||||
cacher := &txSenderCacher{
|
||||
tasks: make(chan *txSenderCacherRequest, threads),
|
||||
threads: threads,
|
||||
}
|
||||
for i := 0; i < threads; i++ {
|
||||
go cacher.cache()
|
||||
}
|
||||
return cacher
|
||||
}
|
||||
|
||||
// cache is an infinite loop, caching transaction senders from various forms of
|
||||
// data structures.
|
||||
func (cacher *txSenderCacher) cache() {
|
||||
for task := range cacher.tasks {
|
||||
for i := 0; i < len(task.txs); i += task.inc {
|
||||
types.Sender(task.signer, task.txs[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recover recovers the senders from a batch of transactions and caches them
|
||||
// back into the same data structures. There is no validation being done, nor
|
||||
// any reaction to invalid signatures. That is up to calling code later.
|
||||
func (cacher *txSenderCacher) recover(signer types.Signer, txs []*types.Transaction) {
|
||||
// If there's nothing to recover, abort
|
||||
if len(txs) == 0 {
|
||||
return
|
||||
}
|
||||
// Ensure we have meaningful task sizes and schedule the recoveries
|
||||
tasks := cacher.threads
|
||||
if len(txs) < tasks*4 {
|
||||
tasks = (len(txs) + 3) / 4
|
||||
}
|
||||
for i := 0; i < tasks; i++ {
|
||||
cacher.tasks <- &txSenderCacherRequest{
|
||||
signer: signer,
|
||||
txs: txs[i:],
|
||||
inc: tasks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recoverFromBlocks recovers the senders from a batch of blocks and caches them
|
||||
// back into the same data structures. There is no validation being done, nor
|
||||
// any reaction to invalid signatures. That is up to calling code later.
|
||||
func (cacher *txSenderCacher) recoverFromBlocks(signer types.Signer, blocks []*types.Block) {
|
||||
count := 0
|
||||
for _, block := range blocks {
|
||||
count += len(block.Transactions())
|
||||
}
|
||||
txs := make([]*types.Transaction, 0, count)
|
||||
for _, block := range blocks {
|
||||
txs = append(txs, block.Transactions()...)
|
||||
}
|
||||
cacher.recover(signer, txs)
|
||||
}
|
||||
+26
-9
@@ -34,7 +34,7 @@ var errNoActiveJournal = errors.New("no active journal")
|
||||
// devNull is a WriteCloser that just discards anything written into it. Its
|
||||
// goal is to allow the transaction journal to write into a fake journal when
|
||||
// loading transactions on startup without printing warnings due to no file
|
||||
// being readt for write.
|
||||
// being read for write.
|
||||
type devNull struct{}
|
||||
|
||||
func (*devNull) Write(p []byte) (n int, err error) { return len(p), nil }
|
||||
@@ -56,8 +56,8 @@ func newTxJournal(path string) *txJournal {
|
||||
|
||||
// load parses a transaction journal dump from disk, loading its contents into
|
||||
// the specified pool.
|
||||
func (journal *txJournal) load(add func(*types.Transaction) error) error {
|
||||
// Skip the parsing if the journal file doens't exist at all
|
||||
func (journal *txJournal) load(add func([]*types.Transaction) []error) error {
|
||||
// Skip the parsing if the journal file doesn't exist at all
|
||||
if _, err := os.Stat(journal.path); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
@@ -76,7 +76,21 @@ func (journal *txJournal) load(add func(*types.Transaction) error) error {
|
||||
stream := rlp.NewStream(input, 0)
|
||||
total, dropped := 0, 0
|
||||
|
||||
var failure error
|
||||
// Create a method to load a limited batch of transactions and bump the
|
||||
// appropriate progress counters. Then use this method to load all the
|
||||
// journaled transactions in small-ish batches.
|
||||
loadBatch := func(txs types.Transactions) {
|
||||
for _, err := range add(txs) {
|
||||
if err != nil {
|
||||
log.Debug("Failed to add journaled transaction", "err", err)
|
||||
dropped++
|
||||
}
|
||||
}
|
||||
}
|
||||
var (
|
||||
failure error
|
||||
batch types.Transactions
|
||||
)
|
||||
for {
|
||||
// Parse the next transaction and terminate on error
|
||||
tx := new(types.Transaction)
|
||||
@@ -84,14 +98,17 @@ func (journal *txJournal) load(add func(*types.Transaction) error) error {
|
||||
if err != io.EOF {
|
||||
failure = err
|
||||
}
|
||||
if batch.Len() > 0 {
|
||||
loadBatch(batch)
|
||||
}
|
||||
break
|
||||
}
|
||||
// Import the transaction and bump the appropriate progress counters
|
||||
// New transaction parsed, queue up for later, import if threshold is reached
|
||||
total++
|
||||
if err = add(tx); err != nil {
|
||||
log.Debug("Failed to add journaled transaction", "err", err)
|
||||
dropped++
|
||||
continue
|
||||
|
||||
if batch = append(batch, tx); batch.Len() > 1024 {
|
||||
loadBatch(batch)
|
||||
batch = batch[:0]
|
||||
}
|
||||
}
|
||||
log.Info("Loaded local transaction journal", "transactions", total, "dropped", dropped)
|
||||
|
||||
+26
-14
@@ -367,9 +367,20 @@ func (l *txList) Flatten() types.Transactions {
|
||||
// price-sorted transactions to discard when the pool fills up.
|
||||
type priceHeap []*types.Transaction
|
||||
|
||||
func (h priceHeap) Len() int { return len(h) }
|
||||
func (h priceHeap) Less(i, j int) bool { return h[i].GasPrice().Cmp(h[j].GasPrice()) < 0 }
|
||||
func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
func (h priceHeap) Len() int { return len(h) }
|
||||
func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
|
||||
func (h priceHeap) Less(i, j int) bool {
|
||||
// Sort primarily by price, returning the cheaper one
|
||||
switch h[i].GasPrice().Cmp(h[j].GasPrice()) {
|
||||
case -1:
|
||||
return true
|
||||
case 1:
|
||||
return false
|
||||
}
|
||||
// If the prices match, stabilize via nonces (high nonce is worse)
|
||||
return h[i].Nonce() > h[j].Nonce()
|
||||
}
|
||||
|
||||
func (h *priceHeap) Push(x interface{}) {
|
||||
*h = append(*h, x.(*types.Transaction))
|
||||
@@ -386,13 +397,13 @@ func (h *priceHeap) Pop() interface{} {
|
||||
// txPricedList is a price-sorted heap to allow operating on transactions pool
|
||||
// contents in a price-incrementing way.
|
||||
type txPricedList struct {
|
||||
all *map[common.Hash]*types.Transaction // Pointer to the map of all transactions
|
||||
items *priceHeap // Heap of prices of all the stored transactions
|
||||
stales int // Number of stale price points to (re-heap trigger)
|
||||
all *txLookup // Pointer to the map of all transactions
|
||||
items *priceHeap // Heap of prices of all the stored transactions
|
||||
stales int // Number of stale price points to (re-heap trigger)
|
||||
}
|
||||
|
||||
// newTxPricedList creates a new price-sorted transaction heap.
|
||||
func newTxPricedList(all *map[common.Hash]*types.Transaction) *txPricedList {
|
||||
func newTxPricedList(all *txLookup) *txPricedList {
|
||||
return &txPricedList{
|
||||
all: all,
|
||||
items: new(priceHeap),
|
||||
@@ -414,17 +425,18 @@ func (l *txPricedList) Removed() {
|
||||
return
|
||||
}
|
||||
// Seems we've reached a critical number of stale transactions, reheap
|
||||
reheap := make(priceHeap, 0, len(*l.all))
|
||||
reheap := make(priceHeap, 0, l.all.Count())
|
||||
|
||||
l.stales, l.items = 0, &reheap
|
||||
for _, tx := range *l.all {
|
||||
l.all.Range(func(hash common.Hash, tx *types.Transaction) bool {
|
||||
*l.items = append(*l.items, tx)
|
||||
}
|
||||
return true
|
||||
})
|
||||
heap.Init(l.items)
|
||||
}
|
||||
|
||||
// Cap finds all the transactions below the given price threshold, drops them
|
||||
// from the priced list and returs them for further removal from the entire pool.
|
||||
// from the priced list and returns them for further removal from the entire pool.
|
||||
func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transactions {
|
||||
drop := make(types.Transactions, 0, 128) // Remote underpriced transactions to drop
|
||||
save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep
|
||||
@@ -432,7 +444,7 @@ func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transact
|
||||
for len(*l.items) > 0 {
|
||||
// Discard stale transactions if found during cleanup
|
||||
tx := heap.Pop(l.items).(*types.Transaction)
|
||||
if _, ok := (*l.all)[tx.Hash()]; !ok {
|
||||
if l.all.Get(tx.Hash()) == nil {
|
||||
l.stales--
|
||||
continue
|
||||
}
|
||||
@@ -464,7 +476,7 @@ func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) boo
|
||||
// Discard stale price points if found at the heap start
|
||||
for len(*l.items) > 0 {
|
||||
head := []*types.Transaction(*l.items)[0]
|
||||
if _, ok := (*l.all)[head.Hash()]; !ok {
|
||||
if l.all.Get(head.Hash()) == nil {
|
||||
l.stales--
|
||||
heap.Pop(l.items)
|
||||
continue
|
||||
@@ -489,7 +501,7 @@ func (l *txPricedList) Discard(count int, local *accountSet) types.Transactions
|
||||
for len(*l.items) > 0 && count > 0 {
|
||||
// Discard stale transactions if found during cleanup
|
||||
tx := heap.Pop(l.items).(*types.Transaction)
|
||||
if _, ok := (*l.all)[tx.Hash()]; !ok {
|
||||
if l.all.Get(tx.Hash()) == nil {
|
||||
l.stales--
|
||||
continue
|
||||
}
|
||||
|
||||
+180
-78
@@ -38,8 +38,6 @@ import (
|
||||
const (
|
||||
// chainHeadChanSize is the size of channel listening to ChainHeadEvent.
|
||||
chainHeadChanSize = 10
|
||||
// rmTxChanSize is the size of channel listening to RemovedTransactionEvent.
|
||||
rmTxChanSize = 10
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -125,14 +123,15 @@ type blockChain interface {
|
||||
|
||||
// TxPoolConfig are the configuration parameters of the transaction pool.
|
||||
type TxPoolConfig struct {
|
||||
NoLocals bool // Whether local transaction handling should be disabled
|
||||
Journal string // Journal of local transactions to survive node restarts
|
||||
Rejournal time.Duration // Time interval to regenerate the local transaction journal
|
||||
Locals []common.Address // Addresses that should be treated by default as local
|
||||
NoLocals bool // Whether local transaction handling should be disabled
|
||||
Journal string // Journal of local transactions to survive node restarts
|
||||
Rejournal time.Duration // Time interval to regenerate the local transaction journal
|
||||
|
||||
PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool
|
||||
PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce)
|
||||
|
||||
AccountSlots uint64 // Minimum number of executable transaction slots guaranteed per account
|
||||
AccountSlots uint64 // Number of executable transaction slots guaranteed per account
|
||||
GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts
|
||||
AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account
|
||||
GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts
|
||||
@@ -202,11 +201,11 @@ type TxPool struct {
|
||||
locals *accountSet // Set of local transaction to exempt from eviction rules
|
||||
journal *txJournal // Journal of local transaction to back up to disk
|
||||
|
||||
pending map[common.Address]*txList // All currently processable transactions
|
||||
queue map[common.Address]*txList // Queued but non-processable transactions
|
||||
beats map[common.Address]time.Time // Last heartbeat from each known account
|
||||
all map[common.Hash]*types.Transaction // All transactions to allow lookups
|
||||
priced *txPricedList // All transactions sorted by price
|
||||
pending map[common.Address]*txList // All currently processable transactions
|
||||
queue map[common.Address]*txList // Queued but non-processable transactions
|
||||
beats map[common.Address]time.Time // Last heartbeat from each known account
|
||||
all *txLookup // All transactions to allow lookups
|
||||
priced *txPricedList // All transactions sorted by price
|
||||
|
||||
wg sync.WaitGroup // for shutdown sync
|
||||
|
||||
@@ -224,23 +223,27 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
|
||||
config: config,
|
||||
chainconfig: chainconfig,
|
||||
chain: chain,
|
||||
signer: types.NewEIP155Signer(chainconfig.ChainId),
|
||||
signer: types.NewEIP155Signer(chainconfig.ChainID),
|
||||
pending: make(map[common.Address]*txList),
|
||||
queue: make(map[common.Address]*txList),
|
||||
beats: make(map[common.Address]time.Time),
|
||||
all: make(map[common.Hash]*types.Transaction),
|
||||
all: newTxLookup(),
|
||||
chainHeadCh: make(chan ChainHeadEvent, chainHeadChanSize),
|
||||
gasPrice: new(big.Int).SetUint64(config.PriceLimit),
|
||||
}
|
||||
pool.locals = newAccountSet(pool.signer)
|
||||
pool.priced = newTxPricedList(&pool.all)
|
||||
for _, addr := range config.Locals {
|
||||
log.Info("Setting new local account", "address", addr)
|
||||
pool.locals.add(addr)
|
||||
}
|
||||
pool.priced = newTxPricedList(pool.all)
|
||||
pool.reset(nil, chain.CurrentBlock().Header())
|
||||
|
||||
// If local transactions and journaling is enabled, load from disk
|
||||
if !config.NoLocals && config.Journal != "" {
|
||||
pool.journal = newTxJournal(config.Journal)
|
||||
|
||||
if err := pool.journal.load(pool.AddLocal); err != nil {
|
||||
if err := pool.journal.load(pool.AddLocals); err != nil {
|
||||
log.Warn("Failed to load transaction journal", "err", err)
|
||||
}
|
||||
if err := pool.journal.rotate(pool.local()); err != nil {
|
||||
@@ -320,7 +323,7 @@ func (pool *TxPool) loop() {
|
||||
// Any non-locals old enough should be removed
|
||||
if time.Since(pool.beats[addr]) > pool.config.Lifetime {
|
||||
for _, tx := range pool.queue[addr].Flatten() {
|
||||
pool.removeTx(tx.Hash())
|
||||
pool.removeTx(tx.Hash(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,6 +416,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
|
||||
|
||||
// Inject any transactions discarded due to reorgs
|
||||
log.Debug("Reinjecting stale transactions", "count", len(reinject))
|
||||
senderCacher.recover(pool.signer, reinject)
|
||||
pool.addTxsLocked(reinject, false)
|
||||
|
||||
// validate the pool of pending transactions, this will remove
|
||||
@@ -446,9 +450,9 @@ func (pool *TxPool) Stop() {
|
||||
log.Info("Transaction pool stopped")
|
||||
}
|
||||
|
||||
// SubscribeTxPreEvent registers a subscription of TxPreEvent and
|
||||
// SubscribeNewTxsEvent registers a subscription of NewTxsEvent and
|
||||
// starts sending event to the given channel.
|
||||
func (pool *TxPool) SubscribeTxPreEvent(ch chan<- TxPreEvent) event.Subscription {
|
||||
func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- NewTxsEvent) event.Subscription {
|
||||
return pool.scope.Track(pool.txFeed.Subscribe(ch))
|
||||
}
|
||||
|
||||
@@ -468,7 +472,7 @@ func (pool *TxPool) SetGasPrice(price *big.Int) {
|
||||
|
||||
pool.gasPrice = price
|
||||
for _, tx := range pool.priced.Cap(price, pool.locals) {
|
||||
pool.removeTx(tx.Hash())
|
||||
pool.removeTx(tx.Hash(), false)
|
||||
}
|
||||
log.Info("Transaction pool price threshold updated", "price", price)
|
||||
}
|
||||
@@ -535,6 +539,14 @@ func (pool *TxPool) Pending() (map[common.Address]types.Transactions, error) {
|
||||
return pending, nil
|
||||
}
|
||||
|
||||
// Locals retrieves the accounts currently considered local by the pool.
|
||||
func (pool *TxPool) Locals() []common.Address {
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
|
||||
return pool.locals.flatten()
|
||||
}
|
||||
|
||||
// local retrieves all currently known local transactions, groupped by origin
|
||||
// account and sorted by nonce. The returned transaction set is a copy and can be
|
||||
// freely modified by calling code.
|
||||
@@ -607,7 +619,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||
func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
||||
// If the transaction is already known, discard it
|
||||
hash := tx.Hash()
|
||||
if pool.all[hash] != nil {
|
||||
if pool.all.Get(hash) != nil {
|
||||
log.Trace("Discarding already known transaction", "hash", hash)
|
||||
return false, fmt.Errorf("known transaction: %x", hash)
|
||||
}
|
||||
@@ -618,19 +630,19 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
// If the transaction pool is full, discard underpriced transactions
|
||||
if uint64(len(pool.all)) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
|
||||
if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
|
||||
// If the new transaction is underpriced, don't accept it
|
||||
if pool.priced.Underpriced(tx, pool.locals) {
|
||||
if !local && pool.priced.Underpriced(tx, pool.locals) {
|
||||
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
|
||||
underpricedTxCounter.Inc(1)
|
||||
return false, ErrUnderpriced
|
||||
}
|
||||
// New transaction is better than our worse ones, make room for it
|
||||
drop := pool.priced.Discard(len(pool.all)-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals)
|
||||
drop := pool.priced.Discard(pool.all.Count()-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals)
|
||||
for _, tx := range drop {
|
||||
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice())
|
||||
underpricedTxCounter.Inc(1)
|
||||
pool.removeTx(tx.Hash())
|
||||
pool.removeTx(tx.Hash(), false)
|
||||
}
|
||||
}
|
||||
// If the transaction is replacing an already pending one, do directly
|
||||
@@ -644,18 +656,18 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
||||
}
|
||||
// New transaction is better, replace old one
|
||||
if old != nil {
|
||||
delete(pool.all, old.Hash())
|
||||
pool.all.Remove(old.Hash())
|
||||
pool.priced.Removed()
|
||||
pendingReplaceCounter.Inc(1)
|
||||
}
|
||||
pool.all[tx.Hash()] = tx
|
||||
pool.all.Add(tx)
|
||||
pool.priced.Put(tx)
|
||||
pool.journalTx(from, tx)
|
||||
|
||||
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
|
||||
|
||||
// We've directly injected a replacement transaction, notify subsystems
|
||||
go pool.txFeed.Send(TxPreEvent{tx})
|
||||
go pool.txFeed.Send(NewTxsEvent{types.Transactions{tx}})
|
||||
|
||||
return old != nil, nil
|
||||
}
|
||||
@@ -666,7 +678,10 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
||||
}
|
||||
// Mark local addresses and journal local transactions
|
||||
if local {
|
||||
pool.locals.add(from)
|
||||
if !pool.locals.contains(from) {
|
||||
log.Info("Setting new local account", "address", from)
|
||||
pool.locals.add(from)
|
||||
}
|
||||
}
|
||||
pool.journalTx(from, tx)
|
||||
|
||||
@@ -691,12 +706,14 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er
|
||||
}
|
||||
// Discard any previous transaction and mark this
|
||||
if old != nil {
|
||||
delete(pool.all, old.Hash())
|
||||
pool.all.Remove(old.Hash())
|
||||
pool.priced.Removed()
|
||||
queuedReplaceCounter.Inc(1)
|
||||
}
|
||||
pool.all[hash] = tx
|
||||
pool.priced.Put(tx)
|
||||
if pool.all.Get(hash) == nil {
|
||||
pool.all.Add(tx)
|
||||
pool.priced.Put(tx)
|
||||
}
|
||||
return old != nil, nil
|
||||
}
|
||||
|
||||
@@ -712,10 +729,11 @@ func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) {
|
||||
}
|
||||
}
|
||||
|
||||
// promoteTx adds a transaction to the pending (processable) list of transactions.
|
||||
// promoteTx adds a transaction to the pending (processable) list of transactions
|
||||
// and returns whether it was inserted or an older was better.
|
||||
//
|
||||
// Note, this method assumes the pool lock is held!
|
||||
func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) {
|
||||
func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool {
|
||||
// Try to insert the transaction into the pending queue
|
||||
if pool.pending[addr] == nil {
|
||||
pool.pending[addr] = newTxList(true)
|
||||
@@ -725,29 +743,29 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
|
||||
inserted, old := list.Add(tx, pool.config.PriceBump)
|
||||
if !inserted {
|
||||
// An older transaction was better, discard this
|
||||
delete(pool.all, hash)
|
||||
pool.all.Remove(hash)
|
||||
pool.priced.Removed()
|
||||
|
||||
pendingDiscardCounter.Inc(1)
|
||||
return
|
||||
return false
|
||||
}
|
||||
// Otherwise discard any previous transaction and mark this
|
||||
if old != nil {
|
||||
delete(pool.all, old.Hash())
|
||||
pool.all.Remove(old.Hash())
|
||||
pool.priced.Removed()
|
||||
|
||||
pendingReplaceCounter.Inc(1)
|
||||
}
|
||||
// Failsafe to work around direct pending inserts (tests)
|
||||
if pool.all[hash] == nil {
|
||||
pool.all[hash] = tx
|
||||
if pool.all.Get(hash) == nil {
|
||||
pool.all.Add(tx)
|
||||
pool.priced.Put(tx)
|
||||
}
|
||||
// Set the potentially new pending nonce and notify any subsystems of the new tx
|
||||
pool.beats[addr] = time.Now()
|
||||
pool.pendingState.SetNonce(addr, tx.Nonce()+1)
|
||||
|
||||
go pool.txFeed.Send(TxPreEvent{tx})
|
||||
return true
|
||||
}
|
||||
|
||||
// AddLocal enqueues a single transaction into the pool if it is valid, marking
|
||||
@@ -813,11 +831,9 @@ func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) []error {
|
||||
|
||||
for i, tx := range txs {
|
||||
var replace bool
|
||||
if replace, errs[i] = pool.add(tx, local); errs[i] == nil {
|
||||
if !replace {
|
||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||
dirty[from] = struct{}{}
|
||||
}
|
||||
if replace, errs[i] = pool.add(tx, local); errs[i] == nil && !replace {
|
||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||
dirty[from] = struct{}{}
|
||||
}
|
||||
}
|
||||
// Only reprocess the internal state if something was actually added
|
||||
@@ -839,7 +855,7 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
|
||||
|
||||
status := make([]TxStatus, len(hashes))
|
||||
for i, hash := range hashes {
|
||||
if tx := pool.all[hash]; tx != nil {
|
||||
if tx := pool.all.Get(hash); tx != nil {
|
||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||
if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil {
|
||||
status[i] = TxStatusPending
|
||||
@@ -854,38 +870,35 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
|
||||
// Get returns a transaction if it is contained in the pool
|
||||
// and nil otherwise.
|
||||
func (pool *TxPool) Get(hash common.Hash) *types.Transaction {
|
||||
pool.mu.RLock()
|
||||
defer pool.mu.RUnlock()
|
||||
|
||||
return pool.all[hash]
|
||||
return pool.all.Get(hash)
|
||||
}
|
||||
|
||||
// removeTx removes a single transaction from the queue, moving all subsequent
|
||||
// transactions back to the future queue.
|
||||
func (pool *TxPool) removeTx(hash common.Hash) {
|
||||
func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) {
|
||||
// Fetch the transaction we wish to delete
|
||||
tx, ok := pool.all[hash]
|
||||
if !ok {
|
||||
tx := pool.all.Get(hash)
|
||||
if tx == nil {
|
||||
return
|
||||
}
|
||||
addr, _ := types.Sender(pool.signer, tx) // already validated during insertion
|
||||
|
||||
// Remove it from the list of known transactions
|
||||
delete(pool.all, hash)
|
||||
pool.priced.Removed()
|
||||
|
||||
pool.all.Remove(hash)
|
||||
if outofbound {
|
||||
pool.priced.Removed()
|
||||
}
|
||||
// Remove the transaction from the pending lists and reset the account nonce
|
||||
if pending := pool.pending[addr]; pending != nil {
|
||||
if removed, invalids := pending.Remove(tx); removed {
|
||||
// If no more transactions are left, remove the list
|
||||
// If no more pending transactions are left, remove the list
|
||||
if pending.Empty() {
|
||||
delete(pool.pending, addr)
|
||||
delete(pool.beats, addr)
|
||||
} else {
|
||||
// Otherwise postpone any invalidated transactions
|
||||
for _, tx := range invalids {
|
||||
pool.enqueueTx(tx.Hash(), tx)
|
||||
}
|
||||
}
|
||||
// Postpone any invalidated transactions
|
||||
for _, tx := range invalids {
|
||||
pool.enqueueTx(tx.Hash(), tx)
|
||||
}
|
||||
// Update the account nonce if needed
|
||||
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
|
||||
@@ -907,6 +920,9 @@ func (pool *TxPool) removeTx(hash common.Hash) {
|
||||
// future queue to the set of pending transactions. During this process, all
|
||||
// invalidated transactions (low nonce, low balance) are deleted.
|
||||
func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||
// Track the promoted transactions to broadcast them at once
|
||||
var promoted []*types.Transaction
|
||||
|
||||
// Gather all the accounts potentially needing updates
|
||||
if accounts == nil {
|
||||
accounts = make([]common.Address, 0, len(pool.queue))
|
||||
@@ -924,7 +940,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||
for _, tx := range list.Forward(pool.currentState.GetNonce(addr)) {
|
||||
hash := tx.Hash()
|
||||
log.Trace("Removed old queued transaction", "hash", hash)
|
||||
delete(pool.all, hash)
|
||||
pool.all.Remove(hash)
|
||||
pool.priced.Removed()
|
||||
}
|
||||
// Drop all transactions that are too costly (low balance or out of gas)
|
||||
@@ -932,21 +948,23 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||
for _, tx := range drops {
|
||||
hash := tx.Hash()
|
||||
log.Trace("Removed unpayable queued transaction", "hash", hash)
|
||||
delete(pool.all, hash)
|
||||
pool.all.Remove(hash)
|
||||
pool.priced.Removed()
|
||||
queuedNofundsCounter.Inc(1)
|
||||
}
|
||||
// Gather all executable transactions and promote them
|
||||
for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {
|
||||
hash := tx.Hash()
|
||||
log.Trace("Promoting queued transaction", "hash", hash)
|
||||
pool.promoteTx(addr, hash, tx)
|
||||
if pool.promoteTx(addr, hash, tx) {
|
||||
log.Trace("Promoting queued transaction", "hash", hash)
|
||||
promoted = append(promoted, tx)
|
||||
}
|
||||
}
|
||||
// Drop all transactions over the allowed limit
|
||||
if !pool.locals.contains(addr) {
|
||||
for _, tx := range list.Cap(int(pool.config.AccountQueue)) {
|
||||
hash := tx.Hash()
|
||||
delete(pool.all, hash)
|
||||
pool.all.Remove(hash)
|
||||
pool.priced.Removed()
|
||||
queuedRateLimitCounter.Inc(1)
|
||||
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
|
||||
@@ -957,6 +975,10 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||
delete(pool.queue, addr)
|
||||
}
|
||||
}
|
||||
// Notify subsystem for new promoted transactions.
|
||||
if len(promoted) > 0 {
|
||||
go pool.txFeed.Send(NewTxsEvent{promoted})
|
||||
}
|
||||
// If the pending limit is overflown, start equalizing allowances
|
||||
pending := uint64(0)
|
||||
for _, list := range pool.pending {
|
||||
@@ -991,7 +1013,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||
for _, tx := range list.Cap(list.Len() - 1) {
|
||||
// Drop the transaction from the global pools too
|
||||
hash := tx.Hash()
|
||||
delete(pool.all, hash)
|
||||
pool.all.Remove(hash)
|
||||
pool.priced.Removed()
|
||||
|
||||
// Update the account nonce to the dropped transaction
|
||||
@@ -1013,7 +1035,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||
for _, tx := range list.Cap(list.Len() - 1) {
|
||||
// Drop the transaction from the global pools too
|
||||
hash := tx.Hash()
|
||||
delete(pool.all, hash)
|
||||
pool.all.Remove(hash)
|
||||
pool.priced.Removed()
|
||||
|
||||
// Update the account nonce to the dropped transaction
|
||||
@@ -1035,7 +1057,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||
}
|
||||
if queued > pool.config.GlobalQueue {
|
||||
// Sort all accounts with queued transactions by heartbeat
|
||||
addresses := make(addresssByHeartbeat, 0, len(pool.queue))
|
||||
addresses := make(addressesByHeartbeat, 0, len(pool.queue))
|
||||
for addr := range pool.queue {
|
||||
if !pool.locals.contains(addr) { // don't drop locals
|
||||
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
|
||||
@@ -1053,7 +1075,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||
// Drop all transactions if they are less than the overflow
|
||||
if size := uint64(list.Len()); size <= drop {
|
||||
for _, tx := range list.Flatten() {
|
||||
pool.removeTx(tx.Hash())
|
||||
pool.removeTx(tx.Hash(), true)
|
||||
}
|
||||
drop -= size
|
||||
queuedRateLimitCounter.Inc(int64(size))
|
||||
@@ -1062,7 +1084,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) {
|
||||
// Otherwise drop only last few transactions
|
||||
txs := list.Flatten()
|
||||
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
|
||||
pool.removeTx(txs[i].Hash())
|
||||
pool.removeTx(txs[i].Hash(), true)
|
||||
drop--
|
||||
queuedRateLimitCounter.Inc(1)
|
||||
}
|
||||
@@ -1082,7 +1104,7 @@ func (pool *TxPool) demoteUnexecutables() {
|
||||
for _, tx := range list.Forward(nonce) {
|
||||
hash := tx.Hash()
|
||||
log.Trace("Removed old pending transaction", "hash", hash)
|
||||
delete(pool.all, hash)
|
||||
pool.all.Remove(hash)
|
||||
pool.priced.Removed()
|
||||
}
|
||||
// Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
|
||||
@@ -1090,7 +1112,7 @@ func (pool *TxPool) demoteUnexecutables() {
|
||||
for _, tx := range drops {
|
||||
hash := tx.Hash()
|
||||
log.Trace("Removed unpayable pending transaction", "hash", hash)
|
||||
delete(pool.all, hash)
|
||||
pool.all.Remove(hash)
|
||||
pool.priced.Removed()
|
||||
pendingNofundsCounter.Inc(1)
|
||||
}
|
||||
@@ -1099,7 +1121,7 @@ func (pool *TxPool) demoteUnexecutables() {
|
||||
log.Trace("Demoting pending transaction", "hash", hash)
|
||||
pool.enqueueTx(hash, tx)
|
||||
}
|
||||
// If there's a gap in front, warn (should never happen) and postpone all transactions
|
||||
// If there's a gap in front, alert (should never happen) and postpone all transactions
|
||||
if list.Len() > 0 && list.txs.Get(nonce) == nil {
|
||||
for _, tx := range list.Cap(0) {
|
||||
hash := tx.Hash()
|
||||
@@ -1121,17 +1143,18 @@ type addressByHeartbeat struct {
|
||||
heartbeat time.Time
|
||||
}
|
||||
|
||||
type addresssByHeartbeat []addressByHeartbeat
|
||||
type addressesByHeartbeat []addressByHeartbeat
|
||||
|
||||
func (a addresssByHeartbeat) Len() int { return len(a) }
|
||||
func (a addresssByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
|
||||
func (a addresssByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a addressesByHeartbeat) Len() int { return len(a) }
|
||||
func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) }
|
||||
func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// accountSet is simply a set of addresses to check for existence, and a signer
|
||||
// capable of deriving addresses from transactions.
|
||||
type accountSet struct {
|
||||
accounts map[common.Address]struct{}
|
||||
signer types.Signer
|
||||
cache *[]common.Address
|
||||
}
|
||||
|
||||
// newAccountSet creates a new address set with an associated signer for sender
|
||||
@@ -1161,4 +1184,83 @@ func (as *accountSet) containsTx(tx *types.Transaction) bool {
|
||||
// add inserts a new address into the set to track.
|
||||
func (as *accountSet) add(addr common.Address) {
|
||||
as.accounts[addr] = struct{}{}
|
||||
as.cache = nil
|
||||
}
|
||||
|
||||
// flatten returns the list of addresses within this set, also caching it for later
|
||||
// reuse. The returned slice should not be changed!
|
||||
func (as *accountSet) flatten() []common.Address {
|
||||
if as.cache == nil {
|
||||
accounts := make([]common.Address, 0, len(as.accounts))
|
||||
for account := range as.accounts {
|
||||
accounts = append(accounts, account)
|
||||
}
|
||||
as.cache = &accounts
|
||||
}
|
||||
return *as.cache
|
||||
}
|
||||
|
||||
// txLookup is used internally by TxPool to track transactions while allowing lookup without
|
||||
// mutex contention.
|
||||
//
|
||||
// Note, although this type is properly protected against concurrent access, it
|
||||
// is **not** a type that should ever be mutated or even exposed outside of the
|
||||
// transaction pool, since its internal state is tightly coupled with the pools
|
||||
// internal mechanisms. The sole purpose of the type is to permit out-of-bound
|
||||
// peeking into the pool in TxPool.Get without having to acquire the widely scoped
|
||||
// TxPool.mu mutex.
|
||||
type txLookup struct {
|
||||
all map[common.Hash]*types.Transaction
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// newTxLookup returns a new txLookup structure.
|
||||
func newTxLookup() *txLookup {
|
||||
return &txLookup{
|
||||
all: make(map[common.Hash]*types.Transaction),
|
||||
}
|
||||
}
|
||||
|
||||
// Range calls f on each key and value present in the map.
|
||||
func (t *txLookup) Range(f func(hash common.Hash, tx *types.Transaction) bool) {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
for key, value := range t.all {
|
||||
if !f(key, value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a transaction if it exists in the lookup, or nil if not found.
|
||||
func (t *txLookup) Get(hash common.Hash) *types.Transaction {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
return t.all[hash]
|
||||
}
|
||||
|
||||
// Count returns the current number of items in the lookup.
|
||||
func (t *txLookup) Count() int {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
return len(t.all)
|
||||
}
|
||||
|
||||
// Add adds a transaction to the lookup.
|
||||
func (t *txLookup) Add(tx *types.Transaction) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.all[tx.Hash()] = tx
|
||||
}
|
||||
|
||||
// Remove removes a transaction from the lookup.
|
||||
func (t *txLookup) Remove(hash common.Hash) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
delete(t.all, hash)
|
||||
}
|
||||
|
||||
+257
-141
@@ -78,8 +78,7 @@ func pricedTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ec
|
||||
}
|
||||
|
||||
func setupTxPool() (*TxPool, *ecdsa.PrivateKey) {
|
||||
diskdb, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(diskdb))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
key, _ := crypto.GenerateKey()
|
||||
@@ -95,7 +94,7 @@ func validateTxPoolInternals(pool *TxPool) error {
|
||||
|
||||
// Ensure the total transaction set is consistent with pending + queued
|
||||
pending, queued := pool.stats()
|
||||
if total := len(pool.all); total != pending+queued {
|
||||
if total := pool.all.Count(); total != pending+queued {
|
||||
return fmt.Errorf("total transaction count %d != %d pending + %d queued", total, pending, queued)
|
||||
}
|
||||
if priced := pool.priced.items.Len() - pool.priced.stales; priced != pending+queued {
|
||||
@@ -119,21 +118,27 @@ func validateTxPoolInternals(pool *TxPool) error {
|
||||
|
||||
// validateEvents checks that the correct number of transaction addition events
|
||||
// were fired on the pool's event feed.
|
||||
func validateEvents(events chan TxPreEvent, count int) error {
|
||||
for i := 0; i < count; i++ {
|
||||
func validateEvents(events chan NewTxsEvent, count int) error {
|
||||
var received []*types.Transaction
|
||||
|
||||
for len(received) < count {
|
||||
select {
|
||||
case <-events:
|
||||
case ev := <-events:
|
||||
received = append(received, ev.Txs...)
|
||||
case <-time.After(time.Second):
|
||||
return fmt.Errorf("event #%d not fired", i)
|
||||
return fmt.Errorf("event #%d not fired", received)
|
||||
}
|
||||
}
|
||||
if len(received) > count {
|
||||
return fmt.Errorf("more than %d events fired: %v", count, received[count:])
|
||||
}
|
||||
select {
|
||||
case tx := <-events:
|
||||
return fmt.Errorf("more than %d events fired: %v", count, tx.Tx)
|
||||
case ev := <-events:
|
||||
return fmt.Errorf("more than %d events fired: %v", count, ev.Txs)
|
||||
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
// This branch should be "default", but it's a data race between goroutines,
|
||||
// reading the event channel and pushng into it, so better wait a bit ensuring
|
||||
// reading the event channel and pushing into it, so better wait a bit ensuring
|
||||
// really nothing gets injected.
|
||||
}
|
||||
return nil
|
||||
@@ -158,8 +163,7 @@ func (c *testChain) State() (*state.StateDB, error) {
|
||||
// a state change between those fetches.
|
||||
stdb := c.statedb
|
||||
if *c.trigger {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
c.statedb, _ = state.New(common.Hash{}, state.NewDatabase(db))
|
||||
c.statedb, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
// simulate that the new head block included tx0 and tx1
|
||||
c.statedb.SetNonce(c.address, 2)
|
||||
c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether))
|
||||
@@ -175,10 +179,9 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
db, _ = ethdb.NewMemDatabase()
|
||||
key, _ = crypto.GenerateKey()
|
||||
address = crypto.PubkeyToAddress(key.PublicKey)
|
||||
statedb, _ = state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
trigger = false
|
||||
)
|
||||
|
||||
@@ -209,15 +212,10 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) {
|
||||
|
||||
pool.lockedReset(nil, nil)
|
||||
|
||||
pendingTx, err := pool.Pending()
|
||||
_, err := pool.Pending()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not fetch pending transactions: %v", err)
|
||||
}
|
||||
|
||||
for addr, txs := range pendingTx {
|
||||
t.Logf("%0x: %d\n", addr, len(txs))
|
||||
}
|
||||
|
||||
nonce = pool.State().GetNonce(address)
|
||||
if nonce != 2 {
|
||||
t.Fatalf("Invalid nonce, want 2, got %d", nonce)
|
||||
@@ -337,8 +335,7 @@ func TestTransactionChainFork(t *testing.T) {
|
||||
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
resetState := func() {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
statedb.AddBalance(addr, big.NewInt(100000000000000))
|
||||
|
||||
pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
@@ -350,7 +347,7 @@ func TestTransactionChainFork(t *testing.T) {
|
||||
if _, err := pool.add(tx, false); err != nil {
|
||||
t.Error("didn't expect error", err)
|
||||
}
|
||||
pool.removeTx(tx.Hash())
|
||||
pool.removeTx(tx.Hash(), true)
|
||||
|
||||
// reset the pool's internal state
|
||||
resetState()
|
||||
@@ -367,8 +364,7 @@ func TestTransactionDoubleNonce(t *testing.T) {
|
||||
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
resetState := func() {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
statedb.AddBalance(addr, big.NewInt(100000000000000))
|
||||
|
||||
pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
@@ -405,8 +401,8 @@ func TestTransactionDoubleNonce(t *testing.T) {
|
||||
t.Errorf("transaction mismatch: have %x, want %x", tx.Hash(), tx2.Hash())
|
||||
}
|
||||
// Ensure the total transaction count is correct
|
||||
if len(pool.all) != 1 {
|
||||
t.Error("expected 1 total transactions, got", len(pool.all))
|
||||
if pool.all.Count() != 1 {
|
||||
t.Error("expected 1 total transactions, got", pool.all.Count())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,8 +424,8 @@ func TestTransactionMissingNonce(t *testing.T) {
|
||||
if pool.queue[addr].Len() != 1 {
|
||||
t.Error("expected 1 queued transaction, got", pool.queue[addr].Len())
|
||||
}
|
||||
if len(pool.all) != 1 {
|
||||
t.Error("expected 1 total transactions, got", len(pool.all))
|
||||
if pool.all.Count() != 1 {
|
||||
t.Error("expected 1 total transactions, got", pool.all.Count())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,8 +488,8 @@ func TestTransactionDropping(t *testing.T) {
|
||||
if pool.queue[account].Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
|
||||
}
|
||||
if len(pool.all) != 6 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 6)
|
||||
if pool.all.Count() != 6 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
||||
}
|
||||
pool.lockedReset(nil, nil)
|
||||
if pool.pending[account].Len() != 3 {
|
||||
@@ -502,8 +498,8 @@ func TestTransactionDropping(t *testing.T) {
|
||||
if pool.queue[account].Len() != 3 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 3)
|
||||
}
|
||||
if len(pool.all) != 6 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 6)
|
||||
if pool.all.Count() != 6 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 6)
|
||||
}
|
||||
// Reduce the balance of the account, and check that invalidated transactions are dropped
|
||||
pool.currentState.AddBalance(account, big.NewInt(-650))
|
||||
@@ -527,8 +523,8 @@ func TestTransactionDropping(t *testing.T) {
|
||||
if _, ok := pool.queue[account].txs.items[tx12.Nonce()]; ok {
|
||||
t.Errorf("out-of-fund queued transaction present: %v", tx11)
|
||||
}
|
||||
if len(pool.all) != 4 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 4)
|
||||
if pool.all.Count() != 4 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 4)
|
||||
}
|
||||
// Reduce the block gas limit, check that invalidated transactions are dropped
|
||||
pool.chain.(*testBlockChain).gasLimit = 100
|
||||
@@ -546,8 +542,8 @@ func TestTransactionDropping(t *testing.T) {
|
||||
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok {
|
||||
t.Errorf("over-gased queued transaction present: %v", tx11)
|
||||
}
|
||||
if len(pool.all) != 2 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), 2)
|
||||
if pool.all.Count() != 2 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,74 +553,111 @@ func TestTransactionDropping(t *testing.T) {
|
||||
func TestTransactionPostponing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a test account and fund it
|
||||
pool, key := setupTxPool()
|
||||
// Create the pool to test the postponing with
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||
defer pool.Stop()
|
||||
|
||||
account, _ := deriveSender(transaction(0, 0, key))
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000))
|
||||
// Create two test accounts to produce different gap profiles with
|
||||
keys := make([]*ecdsa.PrivateKey, 2)
|
||||
accs := make([]common.Address, len(keys))
|
||||
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
accs[i] = crypto.PubkeyToAddress(keys[i].PublicKey)
|
||||
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(50100))
|
||||
}
|
||||
// Add a batch consecutive pending transactions for validation
|
||||
txns := []*types.Transaction{}
|
||||
for i := 0; i < 100; i++ {
|
||||
var tx *types.Transaction
|
||||
if i%2 == 0 {
|
||||
tx = transaction(uint64(i), 100, key)
|
||||
} else {
|
||||
tx = transaction(uint64(i), 500, key)
|
||||
txs := []*types.Transaction{}
|
||||
for i, key := range keys {
|
||||
|
||||
for j := 0; j < 100; j++ {
|
||||
var tx *types.Transaction
|
||||
if (i+j)%2 == 0 {
|
||||
tx = transaction(uint64(j), 25000, key)
|
||||
} else {
|
||||
tx = transaction(uint64(j), 50000, key)
|
||||
}
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
}
|
||||
for i, err := range pool.AddRemotes(txs) {
|
||||
if err != nil {
|
||||
t.Fatalf("tx %d: failed to add transactions: %v", i, err)
|
||||
}
|
||||
pool.promoteTx(account, tx.Hash(), tx)
|
||||
txns = append(txns, tx)
|
||||
}
|
||||
// Check that pre and post validations leave the pool as is
|
||||
if pool.pending[account].Len() != len(txns) {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), len(txns))
|
||||
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 0)
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
|
||||
}
|
||||
if len(pool.all) != len(txns) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), len(txns))
|
||||
if pool.all.Count() != len(txs) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
||||
}
|
||||
pool.lockedReset(nil, nil)
|
||||
if pool.pending[account].Len() != len(txns) {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pool.pending[account].Len(), len(txns))
|
||||
if pending := pool.pending[accs[0]].Len() + pool.pending[accs[1]].Len(); pending != len(txs) {
|
||||
t.Errorf("pending transaction mismatch: have %d, want %d", pending, len(txs))
|
||||
}
|
||||
if len(pool.queue) != 0 {
|
||||
t.Errorf("queued transaction mismatch: have %d, want %d", pool.queue[account].Len(), 0)
|
||||
t.Errorf("queued accounts mismatch: have %d, want %d", len(pool.queue), 0)
|
||||
}
|
||||
if len(pool.all) != len(txns) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), len(txns))
|
||||
if pool.all.Count() != len(txs) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs))
|
||||
}
|
||||
// Reduce the balance of the account, and check that transactions are reorganised
|
||||
pool.currentState.AddBalance(account, big.NewInt(-750))
|
||||
for _, addr := range accs {
|
||||
pool.currentState.AddBalance(addr, big.NewInt(-1))
|
||||
}
|
||||
pool.lockedReset(nil, nil)
|
||||
|
||||
if _, ok := pool.pending[account].txs.items[txns[0].Nonce()]; !ok {
|
||||
t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txns[0])
|
||||
// The first account's first transaction remains valid, check that subsequent
|
||||
// ones are either filtered out, or queued up for later.
|
||||
if _, ok := pool.pending[accs[0]].txs.items[txs[0].Nonce()]; !ok {
|
||||
t.Errorf("tx %d: valid and funded transaction missing from pending pool: %v", 0, txs[0])
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[txns[0].Nonce()]; ok {
|
||||
t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txns[0])
|
||||
if _, ok := pool.queue[accs[0]].txs.items[txs[0].Nonce()]; ok {
|
||||
t.Errorf("tx %d: valid and funded transaction present in future queue: %v", 0, txs[0])
|
||||
}
|
||||
for i, tx := range txns[1:] {
|
||||
for i, tx := range txs[1:100] {
|
||||
if i%2 == 1 {
|
||||
if _, ok := pool.pending[account].txs.items[tx.Nonce()]; ok {
|
||||
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: valid but future transaction present in pending pool: %v", i+1, tx)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx.Nonce()]; !ok {
|
||||
if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; !ok {
|
||||
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", i+1, tx)
|
||||
}
|
||||
} else {
|
||||
if _, ok := pool.pending[account].txs.items[tx.Nonce()]; ok {
|
||||
if _, ok := pool.pending[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: out-of-fund transaction present in pending pool: %v", i+1, tx)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx.Nonce()]; ok {
|
||||
if _, ok := pool.queue[accs[0]].txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", i+1, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(pool.all) != len(txns)/2 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), len(txns)/2)
|
||||
// The second account's first transaction got invalid, check that all transactions
|
||||
// are either filtered out, or queued up for later.
|
||||
if pool.pending[accs[1]] != nil {
|
||||
t.Errorf("invalidated account still has pending transactions")
|
||||
}
|
||||
for i, tx := range txs[100:] {
|
||||
if i%2 == 1 {
|
||||
if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; !ok {
|
||||
t.Errorf("tx %d: valid but future transaction missing from future queue: %v", 100+i, tx)
|
||||
}
|
||||
} else {
|
||||
if _, ok := pool.queue[accs[1]].txs.items[tx.Nonce()]; ok {
|
||||
t.Errorf("tx %d: out-of-fund transaction present in future queue: %v", 100+i, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
if pool.all.Count() != len(txs)/2 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), len(txs)/2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,7 +675,7 @@ func TestTransactionGapFilling(t *testing.T) {
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan TxPreEvent, testTxPoolConfig.AccountQueue+5)
|
||||
events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -715,8 +748,8 @@ func TestTransactionQueueAccountLimiting(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(pool.all) != int(testTxPoolConfig.AccountQueue) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), testTxPoolConfig.AccountQueue)
|
||||
if pool.all.Count() != int(testTxPoolConfig.AccountQueue) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,8 +769,7 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the limit enforcement with
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
config := testTxPoolConfig
|
||||
@@ -825,8 +857,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) {
|
||||
evictionInterval = time.Second
|
||||
|
||||
// Create the pool to test the non-expiration enforcement
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
config := testTxPoolConfig
|
||||
@@ -895,7 +926,7 @@ func TestTransactionPendingLimiting(t *testing.T) {
|
||||
pool.currentState.AddBalance(account, big.NewInt(1000000))
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan TxPreEvent, testTxPoolConfig.AccountQueue+5)
|
||||
events := make(chan NewTxsEvent, testTxPoolConfig.AccountQueue+5)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -911,8 +942,8 @@ func TestTransactionPendingLimiting(t *testing.T) {
|
||||
t.Errorf("tx %d: queue size mismatch: have %d, want %d", i, pool.queue[account].Len(), 0)
|
||||
}
|
||||
}
|
||||
if len(pool.all) != int(testTxPoolConfig.AccountQueue+5) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", len(pool.all), testTxPoolConfig.AccountQueue+5)
|
||||
if pool.all.Count() != int(testTxPoolConfig.AccountQueue+5) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), testTxPoolConfig.AccountQueue+5)
|
||||
}
|
||||
if err := validateEvents(events, int(testTxPoolConfig.AccountQueue+5)); err != nil {
|
||||
t.Fatalf("event firing failed: %v", err)
|
||||
@@ -949,11 +980,11 @@ func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
|
||||
account2, _ := deriveSender(transaction(0, 0, key2))
|
||||
pool2.currentState.AddBalance(account2, big.NewInt(1000000))
|
||||
|
||||
txns := []*types.Transaction{}
|
||||
txs := []*types.Transaction{}
|
||||
for i := uint64(0); i < testTxPoolConfig.AccountQueue+5; i++ {
|
||||
txns = append(txns, transaction(origin+i, 100000, key2))
|
||||
txs = append(txs, transaction(origin+i, 100000, key2))
|
||||
}
|
||||
pool2.AddRemotes(txns)
|
||||
pool2.AddRemotes(txs)
|
||||
|
||||
// Ensure the batch optimization honors the same pool mechanics
|
||||
if len(pool1.pending) != len(pool2.pending) {
|
||||
@@ -962,8 +993,8 @@ func testTransactionLimitingEquivalency(t *testing.T, origin uint64) {
|
||||
if len(pool1.queue) != len(pool2.queue) {
|
||||
t.Errorf("queued transaction count mismatch: one-by-one algo: %d, batch algo: %d", len(pool1.queue), len(pool2.queue))
|
||||
}
|
||||
if len(pool1.all) != len(pool2.all) {
|
||||
t.Errorf("total transaction count mismatch: one-by-one algo %d, batch algo %d", len(pool1.all), len(pool2.all))
|
||||
if pool1.all.Count() != pool2.all.Count() {
|
||||
t.Errorf("total transaction count mismatch: one-by-one algo %d, batch algo %d", pool1.all.Count(), pool2.all.Count())
|
||||
}
|
||||
if err := validateTxPoolInternals(pool1); err != nil {
|
||||
t.Errorf("pool 1 internal state corrupted: %v", err)
|
||||
@@ -980,8 +1011,7 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the limit enforcement with
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
config := testTxPoolConfig
|
||||
@@ -1027,8 +1057,7 @@ func TestTransactionCapClearsFromAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the limit enforcement with
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
config := testTxPoolConfig
|
||||
@@ -1062,8 +1091,7 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the limit enforcement with
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
config := testTxPoolConfig
|
||||
@@ -1111,20 +1139,19 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||
defer pool.Stop()
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan TxPreEvent, 32)
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Create a number of test accounts and fund them
|
||||
keys := make([]*ecdsa.PrivateKey, 3)
|
||||
keys := make([]*ecdsa.PrivateKey, 4)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
@@ -1136,24 +1163,28 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
txs = append(txs, pricedTransaction(1, 100000, big.NewInt(1), keys[0]))
|
||||
txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[0]))
|
||||
|
||||
txs = append(txs, pricedTransaction(0, 100000, big.NewInt(1), keys[1]))
|
||||
txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[1]))
|
||||
txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[1]))
|
||||
txs = append(txs, pricedTransaction(3, 100000, big.NewInt(2), keys[1]))
|
||||
txs = append(txs, pricedTransaction(2, 100000, big.NewInt(2), keys[1]))
|
||||
|
||||
ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[2])
|
||||
txs = append(txs, pricedTransaction(1, 100000, big.NewInt(2), keys[2]))
|
||||
txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[2]))
|
||||
txs = append(txs, pricedTransaction(3, 100000, big.NewInt(2), keys[2]))
|
||||
|
||||
ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[3])
|
||||
|
||||
// Import the batch and that both pending and queued transactions match up
|
||||
pool.AddRemotes(txs)
|
||||
pool.AddLocal(ltx)
|
||||
|
||||
pending, queued := pool.Stats()
|
||||
if pending != 4 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 4)
|
||||
if pending != 7 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 7)
|
||||
}
|
||||
if queued != 3 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
||||
}
|
||||
if err := validateEvents(events, 4); err != nil {
|
||||
if err := validateEvents(events, 7); err != nil {
|
||||
t.Fatalf("original event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
@@ -1166,8 +1197,8 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
if pending != 2 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||
}
|
||||
if queued != 3 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3)
|
||||
if queued != 5 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 5)
|
||||
}
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
t.Fatalf("reprice event firing failed: %v", err)
|
||||
@@ -1179,7 +1210,10 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); err != ErrUnderpriced {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||
}
|
||||
if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[1])); err != ErrUnderpriced {
|
||||
if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); err != ErrUnderpriced {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||
}
|
||||
if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); err != ErrUnderpriced {
|
||||
t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||
}
|
||||
if err := validateEvents(events, 0); err != nil {
|
||||
@@ -1189,7 +1223,7 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// However we can add local underpriced transactions
|
||||
tx := pricedTransaction(1, 100000, big.NewInt(1), keys[2])
|
||||
tx := pricedTransaction(1, 100000, big.NewInt(1), keys[3])
|
||||
if err := pool.AddLocal(tx); err != nil {
|
||||
t.Fatalf("failed to add underpriced local transaction: %v", err)
|
||||
}
|
||||
@@ -1202,6 +1236,22 @@ func TestTransactionPoolRepricing(t *testing.T) {
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// And we can fill gaps with properly priced transactions
|
||||
if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(2), keys[0])); err != nil {
|
||||
t.Fatalf("failed to add pending transaction: %v", err)
|
||||
}
|
||||
if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(2), keys[1])); err != nil {
|
||||
t.Fatalf("failed to add pending transaction: %v", err)
|
||||
}
|
||||
if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(2), keys[2])); err != nil {
|
||||
t.Fatalf("failed to add queued transaction: %v", err)
|
||||
}
|
||||
if err := validateEvents(events, 5); err != nil {
|
||||
t.Fatalf("post-reprice event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that setting the transaction pool gas price to a higher value does not
|
||||
@@ -1210,8 +1260,7 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||
@@ -1273,8 +1322,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
config := testTxPoolConfig
|
||||
@@ -1285,12 +1333,12 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
defer pool.Stop()
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan TxPreEvent, 32)
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Create a number of test accounts and fund them
|
||||
keys := make([]*ecdsa.PrivateKey, 3)
|
||||
keys := make([]*ecdsa.PrivateKey, 4)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
@@ -1327,13 +1375,13 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced)
|
||||
}
|
||||
// Ensure that adding high priced transactions drops cheap ones, but not own
|
||||
if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil {
|
||||
if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { // +K1:0 => -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que -
|
||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||
}
|
||||
if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); err != nil {
|
||||
if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2
|
||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||
}
|
||||
if err := pool.AddRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil {
|
||||
if err := pool.AddRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3
|
||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||
}
|
||||
pending, queued = pool.Stats()
|
||||
@@ -1343,25 +1391,29 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
if queued != 2 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||
}
|
||||
if err := validateEvents(events, 2); err != nil {
|
||||
t.Fatalf("additional event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Ensure that adding local transactions can push out even higher priced ones
|
||||
tx := pricedTransaction(1, 100000, big.NewInt(0), keys[2])
|
||||
if err := pool.AddLocal(tx); err != nil {
|
||||
t.Fatalf("failed to add underpriced local transaction: %v", err)
|
||||
}
|
||||
pending, queued = pool.Stats()
|
||||
if pending != 2 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||
}
|
||||
if queued != 2 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||
}
|
||||
if err := validateEvents(events, 1); err != nil {
|
||||
t.Fatalf("additional event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Ensure that adding local transactions can push out even higher priced ones
|
||||
ltx = pricedTransaction(1, 100000, big.NewInt(0), keys[2])
|
||||
if err := pool.AddLocal(ltx); err != nil {
|
||||
t.Fatalf("failed to append underpriced local transaction: %v", err)
|
||||
}
|
||||
ltx = pricedTransaction(0, 100000, big.NewInt(0), keys[3])
|
||||
if err := pool.AddLocal(ltx); err != nil {
|
||||
t.Fatalf("failed to add new underpriced local transaction: %v", err)
|
||||
}
|
||||
pending, queued = pool.Stats()
|
||||
if pending != 3 {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
|
||||
}
|
||||
if queued != 1 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1)
|
||||
}
|
||||
if err := validateEvents(events, 2); err != nil {
|
||||
t.Fatalf("local event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
@@ -1369,21 +1421,87 @@ func TestTransactionPoolUnderpricing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that more expensive transactions push out cheap ones from the pool, but
|
||||
// without producing instability by creating gaps that start jumping transactions
|
||||
// back and forth between queued/pending.
|
||||
func TestTransactionPoolStableUnderpricing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
config := testTxPoolConfig
|
||||
config.GlobalSlots = 128
|
||||
config.GlobalQueue = 0
|
||||
|
||||
pool := NewTxPool(config, params.TestChainConfig, blockchain)
|
||||
defer pool.Stop()
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
// Create a number of test accounts and fund them
|
||||
keys := make([]*ecdsa.PrivateKey, 2)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||
}
|
||||
// Fill up the entire queue with the same transaction price points
|
||||
txs := types.Transactions{}
|
||||
for i := uint64(0); i < config.GlobalSlots; i++ {
|
||||
txs = append(txs, pricedTransaction(i, 100000, big.NewInt(1), keys[0]))
|
||||
}
|
||||
pool.AddRemotes(txs)
|
||||
|
||||
pending, queued := pool.Stats()
|
||||
if pending != int(config.GlobalSlots) {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots)
|
||||
}
|
||||
if queued != 0 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
|
||||
}
|
||||
if err := validateEvents(events, int(config.GlobalSlots)); err != nil {
|
||||
t.Fatalf("original event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
// Ensure that adding high priced transactions drops a cheap, but doesn't produce a gap
|
||||
if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil {
|
||||
t.Fatalf("failed to add well priced transaction: %v", err)
|
||||
}
|
||||
pending, queued = pool.Stats()
|
||||
if pending != int(config.GlobalSlots) {
|
||||
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots)
|
||||
}
|
||||
if queued != 0 {
|
||||
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0)
|
||||
}
|
||||
if err := validateEvents(events, 1); err != nil {
|
||||
t.Fatalf("additional event firing failed: %v", err)
|
||||
}
|
||||
if err := validateTxPoolInternals(pool); err != nil {
|
||||
t.Fatalf("pool internal state corrupted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the pool rejects replacement transactions that don't meet the minimum
|
||||
// price bump required.
|
||||
func TestTransactionReplacement(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the pricing enforcement with
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||
defer pool.Stop()
|
||||
|
||||
// Keep track of transaction events to ensure all executables get announced
|
||||
events := make(chan TxPreEvent, 32)
|
||||
events := make(chan NewTxsEvent, 32)
|
||||
sub := pool.txFeed.Subscribe(events)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
@@ -1470,8 +1588,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||
os.Remove(journal)
|
||||
|
||||
// Create the original pool to inject transaction into the journal
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
config := testTxPoolConfig
|
||||
@@ -1569,8 +1686,7 @@ func TestTransactionStatusCheck(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the status retrievals with
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)}
|
||||
|
||||
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||
|
||||
-58
@@ -19,7 +19,6 @@ package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sort"
|
||||
@@ -103,25 +102,6 @@ func (h *Header) Hash() common.Hash {
|
||||
return rlpHash(h)
|
||||
}
|
||||
|
||||
// HashNoNonce returns the hash which is used as input for the proof-of-work search.
|
||||
func (h *Header) HashNoNonce() common.Hash {
|
||||
return rlpHash([]interface{}{
|
||||
h.ParentHash,
|
||||
h.UncleHash,
|
||||
h.Coinbase,
|
||||
h.Root,
|
||||
h.TxHash,
|
||||
h.ReceiptHash,
|
||||
h.Bloom,
|
||||
h.Difficulty,
|
||||
h.Number,
|
||||
h.GasLimit,
|
||||
h.GasUsed,
|
||||
h.Time,
|
||||
h.Extra,
|
||||
})
|
||||
}
|
||||
|
||||
// Size returns the approximate memory used by all internal contents. It is used
|
||||
// to approximate and limit the memory consumption of various caches.
|
||||
func (h *Header) Size() common.StorageSize {
|
||||
@@ -325,10 +305,6 @@ func (b *Block) Header() *Header { return CopyHeader(b.header) }
|
||||
// Body returns the non-header content of the block.
|
||||
func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles} }
|
||||
|
||||
func (b *Block) HashNoNonce() common.Hash {
|
||||
return b.header.HashNoNonce()
|
||||
}
|
||||
|
||||
// Size returns the true RLP encoded storage size of the block, either by encoding
|
||||
// and returning it, or returning a previsouly cached value.
|
||||
func (b *Block) Size() common.StorageSize {
|
||||
@@ -389,40 +365,6 @@ func (b *Block) Hash() common.Hash {
|
||||
return v
|
||||
}
|
||||
|
||||
func (b *Block) String() string {
|
||||
str := fmt.Sprintf(`Block(#%v): Size: %v {
|
||||
MinerHash: %x
|
||||
%v
|
||||
Transactions:
|
||||
%v
|
||||
Uncles:
|
||||
%v
|
||||
}
|
||||
`, b.Number(), b.Size(), b.header.HashNoNonce(), b.header, b.transactions, b.uncles)
|
||||
return str
|
||||
}
|
||||
|
||||
func (h *Header) String() string {
|
||||
return fmt.Sprintf(`Header(%x):
|
||||
[
|
||||
ParentHash: %x
|
||||
UncleHash: %x
|
||||
Coinbase: %x
|
||||
Root: %x
|
||||
TxSha %x
|
||||
ReceiptSha: %x
|
||||
Bloom: %x
|
||||
Difficulty: %v
|
||||
Number: %v
|
||||
GasLimit: %v
|
||||
GasUsed: %v
|
||||
Time: %v
|
||||
Extra: %s
|
||||
MixDigest: %x
|
||||
Nonce: %x
|
||||
]`, h.Hash(), h.ParentHash, h.UncleHash, h.Coinbase, h.Root, h.TxHash, h.ReceiptHash, h.Bloom, h.Difficulty, h.Number, h.GasLimit, h.GasUsed, h.Time, h.Extra, h.MixDigest, h.Nonce)
|
||||
}
|
||||
|
||||
type Blocks []*Block
|
||||
|
||||
type BlockBy func(b1, b2 *Block) bool
|
||||
|
||||
+6
-4
@@ -12,10 +12,11 @@ import (
|
||||
|
||||
var _ = (*receiptMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (r Receipt) MarshalJSON() ([]byte, error) {
|
||||
type Receipt struct {
|
||||
PostState hexutil.Bytes `json:"root"`
|
||||
Status hexutil.Uint `json:"status"`
|
||||
Status hexutil.Uint64 `json:"status"`
|
||||
CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
|
||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Logs []*Log `json:"logs" gencodec:"required"`
|
||||
@@ -25,7 +26,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
var enc Receipt
|
||||
enc.PostState = r.PostState
|
||||
enc.Status = hexutil.Uint(r.Status)
|
||||
enc.Status = hexutil.Uint64(r.Status)
|
||||
enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed)
|
||||
enc.Bloom = r.Bloom
|
||||
enc.Logs = r.Logs
|
||||
@@ -35,10 +36,11 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (r *Receipt) UnmarshalJSON(input []byte) error {
|
||||
type Receipt struct {
|
||||
PostState *hexutil.Bytes `json:"root"`
|
||||
Status *hexutil.Uint `json:"status"`
|
||||
Status *hexutil.Uint64 `json:"status"`
|
||||
CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
|
||||
Bloom *Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Logs []*Log `json:"logs" gencodec:"required"`
|
||||
@@ -54,7 +56,7 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
|
||||
r.PostState = *dec.PostState
|
||||
}
|
||||
if dec.Status != nil {
|
||||
r.Status = uint(*dec.Status)
|
||||
r.Status = uint64(*dec.Status)
|
||||
}
|
||||
if dec.CumulativeGasUsed == nil {
|
||||
return errors.New("missing required field 'cumulativeGasUsed' for Receipt")
|
||||
|
||||
-5
@@ -17,7 +17,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -95,10 +94,6 @@ func (l *Log) DecodeRLP(s *rlp.Stream) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *Log) String() string {
|
||||
return fmt.Sprintf(`log: %x %x %x %x %d %x %d`, l.Address, l.Topics, l.Data, l.TxHash, l.TxIndex, l.BlockHash, l.Index)
|
||||
}
|
||||
|
||||
// LogForStorage is a wrapper around a Log that flattens and parses the entire content of
|
||||
// a log including non-consensus fields.
|
||||
type LogForStorage Log
|
||||
|
||||
+4
-12
@@ -36,17 +36,17 @@ var (
|
||||
|
||||
const (
|
||||
// ReceiptStatusFailed is the status code of a transaction if execution failed.
|
||||
ReceiptStatusFailed = uint(0)
|
||||
ReceiptStatusFailed = uint64(0)
|
||||
|
||||
// ReceiptStatusSuccessful is the status code of a transaction if execution succeeded.
|
||||
ReceiptStatusSuccessful = uint(1)
|
||||
ReceiptStatusSuccessful = uint64(1)
|
||||
)
|
||||
|
||||
// Receipt represents the results of a transaction.
|
||||
type Receipt struct {
|
||||
// Consensus fields
|
||||
PostState []byte `json:"root"`
|
||||
Status uint `json:"status"`
|
||||
Status uint64 `json:"status"`
|
||||
CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
|
||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Logs []*Log `json:"logs" gencodec:"required"`
|
||||
@@ -59,7 +59,7 @@ type Receipt struct {
|
||||
|
||||
type receiptMarshaling struct {
|
||||
PostState hexutil.Bytes
|
||||
Status hexutil.Uint
|
||||
Status hexutil.Uint64
|
||||
CumulativeGasUsed hexutil.Uint64
|
||||
GasUsed hexutil.Uint64
|
||||
}
|
||||
@@ -149,14 +149,6 @@ func (r *Receipt) Size() common.StorageSize {
|
||||
return size
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (r *Receipt) String() string {
|
||||
if len(r.PostState) == 0 {
|
||||
return fmt.Sprintf("receipt{status=%d cgas=%v bloom=%x logs=%v}", r.Status, r.CumulativeGasUsed, r.Bloom, r.Logs)
|
||||
}
|
||||
return fmt.Sprintf("receipt{med=%x cgas=%v bloom=%x logs=%v}", r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs)
|
||||
}
|
||||
|
||||
// ReceiptForStorage is a wrapper around a Receipt that flattens and parses the
|
||||
// entire content of a receipt, as opposed to only the consensus fields originally.
|
||||
type ReceiptForStorage Receipt
|
||||
|
||||
+8
-68
@@ -19,7 +19,6 @@ package types
|
||||
import (
|
||||
"container/heap"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
@@ -34,18 +33,8 @@ import (
|
||||
|
||||
var (
|
||||
ErrInvalidSig = errors.New("invalid transaction v, r, s values")
|
||||
errNoSigner = errors.New("missing signing methods")
|
||||
)
|
||||
|
||||
// deriveSigner makes a *best* guess about which signer to use.
|
||||
func deriveSigner(V *big.Int) Signer {
|
||||
if V.Sign() != 0 && isProtectedV(V) {
|
||||
return NewEIP155Signer(deriveChainId(V))
|
||||
} else {
|
||||
return HomesteadSigner{}
|
||||
}
|
||||
}
|
||||
|
||||
type Transaction struct {
|
||||
data txdata
|
||||
// caches
|
||||
@@ -130,7 +119,7 @@ func isProtectedV(V *big.Int) bool {
|
||||
v := V.Uint64()
|
||||
return v != 27 && v != 28
|
||||
}
|
||||
// anything not 27 or 28 are considered unprotected
|
||||
// anything not 27 or 28 is considered protected
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -262,58 +251,6 @@ func (tx *Transaction) RawSignatureValues() (*big.Int, *big.Int, *big.Int) {
|
||||
return tx.data.V, tx.data.R, tx.data.S
|
||||
}
|
||||
|
||||
func (tx *Transaction) String() string {
|
||||
var from, to string
|
||||
if tx.data.V != nil {
|
||||
// make a best guess about the signer and use that to derive
|
||||
// the sender.
|
||||
signer := deriveSigner(tx.data.V)
|
||||
if f, err := Sender(signer, tx); err != nil { // derive but don't cache
|
||||
from = "[invalid sender: invalid sig]"
|
||||
} else {
|
||||
from = fmt.Sprintf("%x", f[:])
|
||||
}
|
||||
} else {
|
||||
from = "[invalid sender: nil V field]"
|
||||
}
|
||||
|
||||
if tx.data.Recipient == nil {
|
||||
to = "[contract creation]"
|
||||
} else {
|
||||
to = fmt.Sprintf("%x", tx.data.Recipient[:])
|
||||
}
|
||||
enc, _ := rlp.EncodeToBytes(&tx.data)
|
||||
return fmt.Sprintf(`
|
||||
TX(%x)
|
||||
Contract: %v
|
||||
From: %s
|
||||
To: %s
|
||||
Nonce: %v
|
||||
GasPrice: %#x
|
||||
GasLimit %#x
|
||||
Value: %#x
|
||||
Data: 0x%x
|
||||
V: %#x
|
||||
R: %#x
|
||||
S: %#x
|
||||
Hex: %x
|
||||
`,
|
||||
tx.Hash(),
|
||||
tx.data.Recipient == nil,
|
||||
from,
|
||||
to,
|
||||
tx.data.AccountNonce,
|
||||
tx.data.Price,
|
||||
tx.data.GasLimit,
|
||||
tx.data.Amount,
|
||||
tx.data.Payload,
|
||||
tx.data.V,
|
||||
tx.data.R,
|
||||
tx.data.S,
|
||||
enc,
|
||||
)
|
||||
}
|
||||
|
||||
// Transactions is a Transaction slice type for basic sorting.
|
||||
type Transactions []*Transaction
|
||||
|
||||
@@ -329,9 +266,9 @@ func (s Transactions) GetRlp(i int) []byte {
|
||||
return enc
|
||||
}
|
||||
|
||||
// TxDifference returns a new set t which is the difference between a to b.
|
||||
func TxDifference(a, b Transactions) (keep Transactions) {
|
||||
keep = make(Transactions, 0, len(a))
|
||||
// TxDifference returns a new set which is the difference between a and b.
|
||||
func TxDifference(a, b Transactions) Transactions {
|
||||
keep := make(Transactions, 0, len(a))
|
||||
|
||||
remove := make(map[common.Hash]struct{})
|
||||
for _, tx := range b {
|
||||
@@ -393,11 +330,14 @@ type TransactionsByPriceAndNonce struct {
|
||||
func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions) *TransactionsByPriceAndNonce {
|
||||
// Initialize a price based heap with the head transactions
|
||||
heads := make(TxByPrice, 0, len(txs))
|
||||
for _, accTxs := range txs {
|
||||
for from, accTxs := range txs {
|
||||
heads = append(heads, accTxs[0])
|
||||
// Ensure the sender address is from the signer
|
||||
acc, _ := Sender(signer, accTxs[0])
|
||||
txs[acc] = accTxs[1:]
|
||||
if from != acc {
|
||||
delete(txs, from)
|
||||
}
|
||||
}
|
||||
heap.Init(&heads)
|
||||
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
|
||||
var signer Signer
|
||||
switch {
|
||||
case config.IsEIP155(blockNumber):
|
||||
signer = NewEIP155Signer(config.ChainId)
|
||||
signer = NewEIP155Signer(config.ChainID)
|
||||
case config.IsHomestead(blockNumber):
|
||||
signer = HomesteadSigner{}
|
||||
default:
|
||||
|
||||
+7
-22
@@ -165,28 +165,13 @@ func TestTransactionPriceNonceSort(t *testing.T) {
|
||||
t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce())
|
||||
}
|
||||
}
|
||||
// Find the previous and next nonce of this account
|
||||
prev, next := i-1, i+1
|
||||
for j := i - 1; j >= 0; j-- {
|
||||
if fromj, _ := Sender(signer, txs[j]); fromi == fromj {
|
||||
prev = j
|
||||
break
|
||||
}
|
||||
}
|
||||
for j := i + 1; j < len(txs); j++ {
|
||||
if fromj, _ := Sender(signer, txs[j]); fromi == fromj {
|
||||
next = j
|
||||
break
|
||||
}
|
||||
}
|
||||
// Make sure that in between the neighbor nonces, the transaction is correctly positioned price wise
|
||||
for j := prev + 1; j < next; j++ {
|
||||
fromj, _ := Sender(signer, txs[j])
|
||||
if j < i && txs[j].GasPrice().Cmp(txi.GasPrice()) < 0 {
|
||||
t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", j, fromj[:4], txs[j].GasPrice(), i, fromi[:4], txi.GasPrice())
|
||||
}
|
||||
if j > i && txs[j].GasPrice().Cmp(txi.GasPrice()) > 0 {
|
||||
t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) > tx #%d (A=%x P=%v)", j, fromj[:4], txs[j].GasPrice(), i, fromi[:4], txi.GasPrice())
|
||||
|
||||
// If the next tx has different from account, the price must be lower than the current one
|
||||
if i+1 < len(txs) {
|
||||
next := txs[i+1]
|
||||
fromNext, _ := Sender(signer, next)
|
||||
if fromi != fromNext && txi.GasPrice().Cmp(next.GasPrice()) < 0 {
|
||||
t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -40,7 +40,7 @@ type AccountRef common.Address
|
||||
func (ar AccountRef) Address() common.Address { return (common.Address)(ar) }
|
||||
|
||||
// Contract represents an ethereum contract in the state database. It contains
|
||||
// the the contract code, calling arguments. Contract implements ContractRef
|
||||
// the contract code, calling arguments. Contract implements ContractRef
|
||||
type Contract struct {
|
||||
// CallerAddress is the result of the caller which initialised this
|
||||
// contract. However when the "call method" is delegated this value
|
||||
@@ -139,15 +139,15 @@ func (c *Contract) Value() *big.Int {
|
||||
}
|
||||
|
||||
// SetCode sets the code to the contract
|
||||
func (self *Contract) SetCode(hash common.Hash, code []byte) {
|
||||
self.Code = code
|
||||
self.CodeHash = hash
|
||||
func (c *Contract) SetCode(hash common.Hash, code []byte) {
|
||||
c.Code = code
|
||||
c.CodeHash = hash
|
||||
}
|
||||
|
||||
// SetCallCode sets the code of the contract and address of the backing data
|
||||
// object
|
||||
func (self *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
|
||||
self.Code = code
|
||||
self.CodeHash = hash
|
||||
self.CodeAddr = addr
|
||||
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
|
||||
c.Code = code
|
||||
c.CodeHash = hash
|
||||
c.CodeAddr = addr
|
||||
}
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ func (c *sha256hash) Run(input []byte) ([]byte, error) {
|
||||
return h[:], nil
|
||||
}
|
||||
|
||||
// RIPMED160 implemented as a native contract.
|
||||
// RIPEMD160 implemented as a native contract.
|
||||
type ripemd160hash struct{}
|
||||
|
||||
// RequiredGas returns the gas required to execute the pre-compiled contract.
|
||||
|
||||
+3
-14
@@ -17,19 +17,8 @@
|
||||
/*
|
||||
Package vm implements the Ethereum Virtual Machine.
|
||||
|
||||
The vm package implements two EVMs, a byte code VM and a JIT VM. The BC
|
||||
(Byte Code) VM loops over a set of bytes and executes them according to the set
|
||||
of rules defined in the Ethereum yellow paper. When the BC VM is invoked it
|
||||
invokes the JIT VM in a separate goroutine and compiles the byte code in JIT
|
||||
instructions.
|
||||
|
||||
The JIT VM, when invoked, loops around a set of pre-defined instructions until
|
||||
it either runs of gas, causes an internal error, returns or stops.
|
||||
|
||||
The JIT optimiser attempts to pre-compile instructions in to chunks or segments
|
||||
such as multiple PUSH operations and static JUMPs. It does this by analysing the
|
||||
opcodes and attempts to match certain regions to known sets. Whenever the
|
||||
optimiser finds said segments it creates a new instruction and replaces the
|
||||
first occurrence in the sequence.
|
||||
The vm package implements one EVM, a byte code VM. The BC (Byte Code) VM loops
|
||||
over a set of bytes and executes them according to the set of rules defined
|
||||
in the Ethereum yellow paper.
|
||||
*/
|
||||
package vm
|
||||
|
||||
+2
@@ -18,6 +18,7 @@ package vm
|
||||
|
||||
import "errors"
|
||||
|
||||
// List execution errors
|
||||
var (
|
||||
ErrOutOfGas = errors.New("out of gas")
|
||||
ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas")
|
||||
@@ -25,4 +26,5 @@ var (
|
||||
ErrTraceLimitReached = errors.New("the number of logs reached the specified limit")
|
||||
ErrInsufficientBalance = errors.New("insufficient balance for transfer")
|
||||
ErrContractAddressCollision = errors.New("contract address collision")
|
||||
ErrNoCompatibleInterpreter = errors.New("no compatible interpreter")
|
||||
)
|
||||
|
||||
+75
-35
@@ -31,8 +31,10 @@ import (
|
||||
var emptyCodeHash = crypto.Keccak256Hash(nil)
|
||||
|
||||
type (
|
||||
// CanTransferFunc is the signature of a transfer guard function
|
||||
CanTransferFunc func(StateDB, common.Address, *big.Int) bool
|
||||
TransferFunc func(StateDB, common.Address, common.Address, *big.Int)
|
||||
// TransferFunc is the signature of a transfer function
|
||||
TransferFunc func(StateDB, common.Address, common.Address, *big.Int)
|
||||
// GetHashFunc returns the nth block hash in the blockchain
|
||||
// and is used by the BLOCKHASH EVM op code.
|
||||
GetHashFunc func(uint64) common.Hash
|
||||
@@ -49,7 +51,20 @@ func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) {
|
||||
return RunPrecompiledContract(p, input, contract)
|
||||
}
|
||||
}
|
||||
return evm.interpreter.Run(contract, input)
|
||||
for _, interpreter := range evm.interpreters {
|
||||
if interpreter.CanRun(contract.Code) {
|
||||
if evm.interpreter != interpreter {
|
||||
// Ensure that the interpreter pointer is set back
|
||||
// to its current value upon return.
|
||||
defer func(i Interpreter) {
|
||||
evm.interpreter = i
|
||||
}(evm.interpreter)
|
||||
evm.interpreter = interpreter
|
||||
}
|
||||
return interpreter.Run(contract, input)
|
||||
}
|
||||
}
|
||||
return nil, ErrNoCompatibleInterpreter
|
||||
}
|
||||
|
||||
// Context provides the EVM with auxiliary information. Once provided
|
||||
@@ -101,7 +116,8 @@ type EVM struct {
|
||||
vmConfig Config
|
||||
// global (to this context) ethereum virtual machine
|
||||
// used throughout the execution of the tx.
|
||||
interpreter *Interpreter
|
||||
interpreters []Interpreter
|
||||
interpreter Interpreter
|
||||
// abort is used to abort the EVM calling operations
|
||||
// NOTE: must be set atomically
|
||||
abort int32
|
||||
@@ -111,18 +127,21 @@ type EVM struct {
|
||||
callGasTemp uint64
|
||||
}
|
||||
|
||||
// NewEVM retutrns a new EVM . The returned EVM is not thread safe and should
|
||||
// NewEVM returns a new EVM. The returned EVM is not thread safe and should
|
||||
// only ever be used *once*.
|
||||
func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM {
|
||||
evm := &EVM{
|
||||
Context: ctx,
|
||||
StateDB: statedb,
|
||||
vmConfig: vmConfig,
|
||||
chainConfig: chainConfig,
|
||||
chainRules: chainConfig.Rules(ctx.BlockNumber),
|
||||
Context: ctx,
|
||||
StateDB: statedb,
|
||||
vmConfig: vmConfig,
|
||||
chainConfig: chainConfig,
|
||||
chainRules: chainConfig.Rules(ctx.BlockNumber),
|
||||
interpreters: make([]Interpreter, 1),
|
||||
}
|
||||
|
||||
evm.interpreter = NewInterpreter(evm, vmConfig)
|
||||
evm.interpreters[0] = NewEVMInterpreter(evm, vmConfig)
|
||||
evm.interpreter = evm.interpreters[0]
|
||||
|
||||
return evm
|
||||
}
|
||||
|
||||
@@ -132,6 +151,11 @@ func (evm *EVM) Cancel() {
|
||||
atomic.StoreInt32(&evm.abort, 1)
|
||||
}
|
||||
|
||||
// Interpreter returns the current interpreter
|
||||
func (evm *EVM) Interpreter() Interpreter {
|
||||
return evm.interpreter
|
||||
}
|
||||
|
||||
// Call executes the contract associated with the addr with the given input as
|
||||
// parameters. It also handles any necessary value transfer required and takes
|
||||
// the necessary steps to create accounts and reverses the state in case of an
|
||||
@@ -160,6 +184,11 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
precompiles = PrecompiledContractsByzantium
|
||||
}
|
||||
if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
|
||||
// Calling a non existing account, don't do anything, but ping the tracer
|
||||
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
|
||||
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
|
||||
}
|
||||
return nil, gas, nil
|
||||
}
|
||||
evm.StateDB.CreateAccount(addr)
|
||||
@@ -221,7 +250,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||
to = AccountRef(caller.Address())
|
||||
)
|
||||
// initialise a new contract and set the code that is to be used by the
|
||||
// E The contract is a scoped evmironment for this execution context
|
||||
// EVM. The contract is a scoped environment for this execution context
|
||||
// only.
|
||||
contract := NewContract(caller, to, value, gas)
|
||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||
@@ -284,9 +313,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||
// Make sure the readonly is only set if we aren't in readonly yet
|
||||
// this makes also sure that the readonly flag isn't removed for
|
||||
// child calls.
|
||||
if !evm.interpreter.readOnly {
|
||||
evm.interpreter.readOnly = true
|
||||
defer func() { evm.interpreter.readOnly = false }()
|
||||
if !evm.interpreter.IsReadOnly() {
|
||||
evm.interpreter.SetReadOnly(true)
|
||||
defer func() { evm.interpreter.SetReadOnly(false) }()
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -312,9 +341,8 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||
return ret, contract.Gas, err
|
||||
}
|
||||
|
||||
// Create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||
|
||||
// create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) create(caller ContractRef, code []byte, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
|
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
@@ -323,39 +351,38 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
|
||||
return nil, common.Address{}, gas, ErrInsufficientBalance
|
||||
}
|
||||
// Ensure there's no existing contract already at the designated address
|
||||
nonce := evm.StateDB.GetNonce(caller.Address())
|
||||
evm.StateDB.SetNonce(caller.Address(), nonce+1)
|
||||
|
||||
contractAddr = crypto.CreateAddress(caller.Address(), nonce)
|
||||
contractHash := evm.StateDB.GetCodeHash(contractAddr)
|
||||
if evm.StateDB.GetNonce(contractAddr) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
|
||||
// Ensure there's no existing contract already at the designated address
|
||||
contractHash := evm.StateDB.GetCodeHash(address)
|
||||
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
|
||||
return nil, common.Address{}, 0, ErrContractAddressCollision
|
||||
}
|
||||
// Create a new account on the state
|
||||
snapshot := evm.StateDB.Snapshot()
|
||||
evm.StateDB.CreateAccount(contractAddr)
|
||||
evm.StateDB.CreateAccount(address)
|
||||
if evm.ChainConfig().IsEIP158(evm.BlockNumber) {
|
||||
evm.StateDB.SetNonce(contractAddr, 1)
|
||||
evm.StateDB.SetNonce(address, 1)
|
||||
}
|
||||
evm.Transfer(evm.StateDB, caller.Address(), contractAddr, value)
|
||||
evm.Transfer(evm.StateDB, caller.Address(), address, value)
|
||||
|
||||
// initialise a new contract and set the code that is to be used by the
|
||||
// E The contract is a scoped evmironment for this execution context
|
||||
// EVM. The contract is a scoped environment for this execution context
|
||||
// only.
|
||||
contract := NewContract(caller, AccountRef(contractAddr), value, gas)
|
||||
contract.SetCallCode(&contractAddr, crypto.Keccak256Hash(code), code)
|
||||
contract := NewContract(caller, AccountRef(address), value, gas)
|
||||
contract.SetCallCode(&address, crypto.Keccak256Hash(code), code)
|
||||
|
||||
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
||||
return nil, contractAddr, gas, nil
|
||||
return nil, address, gas, nil
|
||||
}
|
||||
|
||||
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||
evm.vmConfig.Tracer.CaptureStart(caller.Address(), contractAddr, true, code, gas, value)
|
||||
evm.vmConfig.Tracer.CaptureStart(caller.Address(), address, true, code, gas, value)
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
ret, err = run(evm, contract, nil)
|
||||
ret, err := run(evm, contract, nil)
|
||||
|
||||
// check whether the max code size has been exceeded
|
||||
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
|
||||
@@ -366,7 +393,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||
if err == nil && !maxCodeSizeExceeded {
|
||||
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
||||
if contract.UseGas(createDataGas) {
|
||||
evm.StateDB.SetCode(contractAddr, ret)
|
||||
evm.StateDB.SetCode(address, ret)
|
||||
} else {
|
||||
err = ErrCodeStoreOutOfGas
|
||||
}
|
||||
@@ -388,11 +415,24 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||
}
|
||||
return ret, contractAddr, contract.Gas, err
|
||||
return ret, address, contract.Gas, err
|
||||
|
||||
}
|
||||
|
||||
// Create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
|
||||
return evm.create(caller, code, gas, value, contractAddr)
|
||||
}
|
||||
|
||||
// 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:]
|
||||
// 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 *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||
contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), code)
|
||||
return evm.create(caller, code, gas, endowment, contractAddr)
|
||||
}
|
||||
|
||||
// ChainConfig returns the environment's chain configuration
|
||||
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
|
||||
|
||||
// Interpreter returns the EVM interpreter
|
||||
func (evm *EVM) Interpreter() *Interpreter { return evm.interpreter }
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// Gas costs
|
||||
const (
|
||||
GasQuickStep uint64 = 2
|
||||
GasFastestStep uint64 = 3
|
||||
|
||||
+19
-3
@@ -124,12 +124,12 @@ func gasSStore(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, m
|
||||
// 1. From a zero-value address to a non-zero value (NEW VALUE)
|
||||
// 2. From a non-zero value address to a zero-value address (DELETE)
|
||||
// 3. From a non-zero to a non-zero (CHANGE)
|
||||
if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) {
|
||||
if val == (common.Hash{}) && y.Sign() != 0 {
|
||||
// 0 => non 0
|
||||
return params.SstoreSetGas, nil
|
||||
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) {
|
||||
} else if val != (common.Hash{}) && y.Sign() == 0 {
|
||||
// non 0 => 0
|
||||
evm.StateDB.AddRefund(params.SstoreRefundGas)
|
||||
|
||||
return params.SstoreClearGas, nil
|
||||
} else {
|
||||
// non 0 => non 0 (or 0 => 0)
|
||||
@@ -241,6 +241,10 @@ func gasExtCodeCopy(gt params.GasTable, evm *EVM, contract *Contract, stack *Sta
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasExtCodeHash(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
return gt.ExtcodeHash, nil
|
||||
}
|
||||
|
||||
func gasMLoad(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var overflow bool
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
@@ -289,6 +293,18 @@ func gasCreate(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, m
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasCreate2(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var overflow bool
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, params.Create2Gas); overflow {
|
||||
return 0, errGasUintOverflow
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasBalance(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
return gt.Balance, nil
|
||||
}
|
||||
|
||||
+335
-271
File diff suppressed because it is too large
Load Diff
+93
-16
@@ -30,19 +30,23 @@ type twoOperandTest struct {
|
||||
expected string
|
||||
}
|
||||
|
||||
func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)) {
|
||||
func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)) {
|
||||
var (
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
|
||||
stack = newstack()
|
||||
pc = uint64(0)
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
|
||||
stack = newstack()
|
||||
pc = uint64(0)
|
||||
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
|
||||
)
|
||||
|
||||
env.interpreter = evmInterpreter
|
||||
evmInterpreter.intPool = poolOfIntPools.get()
|
||||
for i, test := range tests {
|
||||
x := new(big.Int).SetBytes(common.Hex2Bytes(test.x))
|
||||
shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y))
|
||||
expected := new(big.Int).SetBytes(common.Hex2Bytes(test.expected))
|
||||
stack.push(x)
|
||||
stack.push(shift)
|
||||
opFn(&pc, env, nil, nil, stack)
|
||||
opFn(&pc, evmInterpreter, nil, nil, stack)
|
||||
actual := stack.pop()
|
||||
if actual.Cmp(expected) != 0 {
|
||||
t.Errorf("Testcase %d, expected %v, got %v", i, expected, actual)
|
||||
@@ -50,13 +54,13 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
|
||||
// Check pool usage
|
||||
// 1.pool is not allowed to contain anything on the stack
|
||||
// 2.pool is not allowed to contain the same pointers twice
|
||||
if env.interpreter.intPool.pool.len() > 0 {
|
||||
if evmInterpreter.intPool.pool.len() > 0 {
|
||||
|
||||
poolvals := make(map[*big.Int]struct{})
|
||||
poolvals[actual] = struct{}{}
|
||||
|
||||
for env.interpreter.intPool.pool.len() > 0 {
|
||||
key := env.interpreter.intPool.get()
|
||||
for evmInterpreter.intPool.pool.len() > 0 {
|
||||
key := evmInterpreter.intPool.get()
|
||||
if _, exist := poolvals[key]; exist {
|
||||
t.Errorf("Testcase %d, pool contains double-entry", i)
|
||||
}
|
||||
@@ -64,13 +68,18 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
|
||||
}
|
||||
}
|
||||
}
|
||||
poolOfIntPools.put(evmInterpreter.intPool)
|
||||
}
|
||||
|
||||
func TestByteOp(t *testing.T) {
|
||||
var (
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
|
||||
stack = newstack()
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
|
||||
stack = newstack()
|
||||
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
|
||||
)
|
||||
|
||||
env.interpreter = evmInterpreter
|
||||
evmInterpreter.intPool = poolOfIntPools.get()
|
||||
tests := []struct {
|
||||
v string
|
||||
th uint64
|
||||
@@ -91,12 +100,13 @@ func TestByteOp(t *testing.T) {
|
||||
th := new(big.Int).SetUint64(test.th)
|
||||
stack.push(val)
|
||||
stack.push(th)
|
||||
opByte(&pc, env, nil, nil, stack)
|
||||
opByte(&pc, evmInterpreter, nil, nil, stack)
|
||||
actual := stack.pop()
|
||||
if actual.Cmp(test.expected) != 0 {
|
||||
t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual)
|
||||
}
|
||||
}
|
||||
poolOfIntPools.put(evmInterpreter.intPool)
|
||||
}
|
||||
|
||||
func TestSHL(t *testing.T) {
|
||||
@@ -161,6 +171,7 @@ func TestSAR(t *testing.T) {
|
||||
|
||||
func TestSGT(t *testing.T) {
|
||||
tests := []twoOperandTest{
|
||||
|
||||
{"0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
|
||||
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
|
||||
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
|
||||
@@ -171,6 +182,8 @@ func TestSGT(t *testing.T) {
|
||||
{"8000000000000000000000000000000000000000000000000000000000000001", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
|
||||
{"8000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000001"},
|
||||
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
|
||||
{"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd", "0000000000000000000000000000000000000000000000000000000000000001"},
|
||||
{"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", "0000000000000000000000000000000000000000000000000000000000000000"},
|
||||
}
|
||||
testTwoOperandOp(t, tests, opSgt)
|
||||
}
|
||||
@@ -187,15 +200,21 @@ func TestSLT(t *testing.T) {
|
||||
{"8000000000000000000000000000000000000000000000000000000000000001", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000"},
|
||||
{"8000000000000000000000000000000000000000000000000000000000000001", "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "0000000000000000000000000000000000000000000000000000000000000000"},
|
||||
{"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "8000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000001"},
|
||||
{"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd", "0000000000000000000000000000000000000000000000000000000000000000"},
|
||||
{"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd", "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb", "0000000000000000000000000000000000000000000000000000000000000001"},
|
||||
}
|
||||
testTwoOperandOp(t, tests, opSlt)
|
||||
}
|
||||
|
||||
func opBenchmark(bench *testing.B, op func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) {
|
||||
func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) {
|
||||
var (
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
|
||||
stack = newstack()
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
|
||||
stack = newstack()
|
||||
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
|
||||
)
|
||||
|
||||
env.interpreter = evmInterpreter
|
||||
evmInterpreter.intPool = poolOfIntPools.get()
|
||||
// convert args
|
||||
byteArgs := make([][]byte, len(args))
|
||||
for i, arg := range args {
|
||||
@@ -208,9 +227,10 @@ func opBenchmark(bench *testing.B, op func(pc *uint64, evm *EVM, contract *Contr
|
||||
a := new(big.Int).SetBytes(arg)
|
||||
stack.push(a)
|
||||
}
|
||||
op(&pc, env, nil, nil, stack)
|
||||
op(&pc, evmInterpreter, nil, nil, stack)
|
||||
stack.pop()
|
||||
}
|
||||
poolOfIntPools.put(evmInterpreter.intPool)
|
||||
}
|
||||
|
||||
func BenchmarkOpAdd64(b *testing.B) {
|
||||
@@ -349,7 +369,11 @@ func BenchmarkOpEq(b *testing.B) {
|
||||
|
||||
opBenchmark(b, opEq, x, y)
|
||||
}
|
||||
|
||||
func BenchmarkOpEq2(b *testing.B) {
|
||||
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
|
||||
y := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201fffffffe"
|
||||
opBenchmark(b, opEq, x, y)
|
||||
}
|
||||
func BenchmarkOpAnd(b *testing.B) {
|
||||
x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
|
||||
y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
|
||||
@@ -412,3 +436,56 @@ func BenchmarkOpSAR(b *testing.B) {
|
||||
|
||||
opBenchmark(b, opSAR, x, y)
|
||||
}
|
||||
func BenchmarkOpIsZero(b *testing.B) {
|
||||
x := "FBCDEF090807060504030201ffffffffFBCDEF090807060504030201ffffffff"
|
||||
opBenchmark(b, opIszero, x)
|
||||
}
|
||||
|
||||
func TestOpMstore(t *testing.T) {
|
||||
var (
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
|
||||
stack = newstack()
|
||||
mem = NewMemory()
|
||||
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
|
||||
)
|
||||
|
||||
env.interpreter = evmInterpreter
|
||||
evmInterpreter.intPool = poolOfIntPools.get()
|
||||
mem.Resize(64)
|
||||
pc := uint64(0)
|
||||
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
|
||||
stack.pushN(new(big.Int).SetBytes(common.Hex2Bytes(v)), big.NewInt(0))
|
||||
opMstore(&pc, evmInterpreter, nil, mem, stack)
|
||||
if got := common.Bytes2Hex(mem.Get(0, 32)); got != v {
|
||||
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
|
||||
}
|
||||
stack.pushN(big.NewInt(0x1), big.NewInt(0))
|
||||
opMstore(&pc, evmInterpreter, nil, mem, stack)
|
||||
if common.Bytes2Hex(mem.Get(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
|
||||
t.Fatalf("Mstore failed to overwrite previous value")
|
||||
}
|
||||
poolOfIntPools.put(evmInterpreter.intPool)
|
||||
}
|
||||
|
||||
func BenchmarkOpMstore(bench *testing.B) {
|
||||
var (
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
|
||||
stack = newstack()
|
||||
mem = NewMemory()
|
||||
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
|
||||
)
|
||||
|
||||
env.interpreter = evmInterpreter
|
||||
evmInterpreter.intPool = poolOfIntPools.get()
|
||||
mem.Resize(64)
|
||||
pc := uint64(0)
|
||||
memStart := big.NewInt(0)
|
||||
value := big.NewInt(0x1337)
|
||||
|
||||
bench.ResetTimer()
|
||||
for i := 0; i < bench.N; i++ {
|
||||
stack.pushN(value, memStart)
|
||||
opMstore(&pc, evmInterpreter, nil, mem, stack)
|
||||
}
|
||||
poolOfIntPools.put(evmInterpreter.intPool)
|
||||
}
|
||||
|
||||
+60
-15
@@ -28,10 +28,6 @@ import (
|
||||
type Config struct {
|
||||
// Debug enabled debugging Interpreter options
|
||||
Debug bool
|
||||
// EnableJit enabled the JIT VM
|
||||
EnableJit bool
|
||||
// ForceJit forces the JIT VM
|
||||
ForceJit bool
|
||||
// Tracer is the op code logger
|
||||
Tracer Tracer
|
||||
// NoRecursion disabled Interpreter call, callcode,
|
||||
@@ -46,10 +42,33 @@ type Config struct {
|
||||
}
|
||||
|
||||
// Interpreter is used to run Ethereum based contracts and will utilise the
|
||||
// passed evmironment to query external sources for state information.
|
||||
// The Interpreter will run the byte code VM or JIT VM based on the passed
|
||||
// passed environment to query external sources for state information.
|
||||
// The Interpreter will run the byte code VM based on the passed
|
||||
// configuration.
|
||||
type Interpreter struct {
|
||||
type Interpreter interface {
|
||||
// Run loops and evaluates the contract's code with the given input data and returns
|
||||
// the return byte-slice and an error if one occurred.
|
||||
Run(contract *Contract, input []byte) ([]byte, error)
|
||||
// CanRun tells if the contract, passed as an argument, can be
|
||||
// run by the current interpreter. This is meant so that the
|
||||
// caller can do something like:
|
||||
//
|
||||
// ```golang
|
||||
// for _, interpreter := range interpreters {
|
||||
// if interpreter.CanRun(contract.code) {
|
||||
// interpreter.Run(contract.code, input)
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
CanRun([]byte) bool
|
||||
// IsReadOnly reports if the interpreter is in read only mode.
|
||||
IsReadOnly() bool
|
||||
// SetReadOnly sets (or unsets) read only mode in the interpreter.
|
||||
SetReadOnly(bool)
|
||||
}
|
||||
|
||||
// EVMInterpreter represents an EVM interpreter
|
||||
type EVMInterpreter struct {
|
||||
evm *EVM
|
||||
cfg Config
|
||||
gasTable params.GasTable
|
||||
@@ -59,8 +78,8 @@ type Interpreter struct {
|
||||
returnData []byte // Last CALL's return data for subsequent reuse
|
||||
}
|
||||
|
||||
// NewInterpreter returns a new instance of the Interpreter.
|
||||
func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
|
||||
// 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.
|
||||
@@ -77,15 +96,14 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
return &Interpreter{
|
||||
return &EVMInterpreter{
|
||||
evm: evm,
|
||||
cfg: cfg,
|
||||
gasTable: evm.ChainConfig().GasTable(evm.BlockNumber),
|
||||
intPool: newIntPool(),
|
||||
}
|
||||
}
|
||||
|
||||
func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack *Stack) error {
|
||||
func (in *EVMInterpreter) enforceRestrictions(op OpCode, operation operation, stack *Stack) error {
|
||||
if in.evm.chainRules.IsByzantium {
|
||||
if in.readOnly {
|
||||
// If the interpreter is operating in readonly mode, make sure no
|
||||
@@ -107,7 +125,15 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack
|
||||
// It's important to note that any errors returned by the interpreter should be
|
||||
// considered a revert-and-consume-all-gas operation except for
|
||||
// errExecutionReverted which means revert-and-keep-gas-left.
|
||||
func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
|
||||
func (in *EVMInterpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
|
||||
if in.intPool == nil {
|
||||
in.intPool = poolOfIntPools.get()
|
||||
defer func() {
|
||||
poolOfIntPools.put(in.intPool)
|
||||
in.intPool = nil
|
||||
}()
|
||||
}
|
||||
|
||||
// Increment the call depth which is restricted to 1024
|
||||
in.evm.depth++
|
||||
defer func() { in.evm.depth-- }()
|
||||
@@ -137,6 +163,9 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
|
||||
)
|
||||
contract.Input = input
|
||||
|
||||
// Reclaim the stack as an int pool when the execution stops
|
||||
defer func() { in.intPool.put(stack.data...) }()
|
||||
|
||||
if in.cfg.Debug {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@@ -188,7 +217,7 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
|
||||
}
|
||||
}
|
||||
// consume the gas and return an error if not enough gas is available.
|
||||
// cost is explicitly set so that the capture state defer method cas get the proper cost
|
||||
// cost is explicitly set so that the capture state defer method can get the proper cost
|
||||
cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize)
|
||||
if err != nil || !contract.UseGas(cost) {
|
||||
return nil, ErrOutOfGas
|
||||
@@ -203,7 +232,7 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
|
||||
}
|
||||
|
||||
// execute the operation
|
||||
res, err := operation.execute(&pc, in.evm, contract, mem, stack)
|
||||
res, err := operation.execute(&pc, in, contract, mem, stack)
|
||||
// verifyPool is a build flag. Pool verification makes sure the integrity
|
||||
// of the integer pool by comparing values to a default value.
|
||||
if verifyPool {
|
||||
@@ -228,3 +257,19 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CanRun tells if the contract, passed as an argument, can be
|
||||
// run by the current interpreter.
|
||||
func (in *EVMInterpreter) CanRun(code []byte) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsReadOnly reports if the interpreter is in read only mode.
|
||||
func (in *EVMInterpreter) IsReadOnly() bool {
|
||||
return in.readOnly
|
||||
}
|
||||
|
||||
// SetReadOnly sets (or unsets) read only mode in the interpreter.
|
||||
func (in *EVMInterpreter) SetReadOnly(ro bool) {
|
||||
in.readOnly = ro
|
||||
}
|
||||
|
||||
+54
-3
@@ -16,7 +16,10 @@
|
||||
|
||||
package vm
|
||||
|
||||
import "math/big"
|
||||
import (
|
||||
"math/big"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var checkVal = big.NewInt(-42)
|
||||
|
||||
@@ -32,24 +35,72 @@ func newIntPool() *intPool {
|
||||
return &intPool{pool: newstack()}
|
||||
}
|
||||
|
||||
// get retrieves a big int from the pool, allocating one if the pool is empty.
|
||||
// Note, the returned int's value is arbitrary and will not be zeroed!
|
||||
func (p *intPool) get() *big.Int {
|
||||
if p.pool.len() > 0 {
|
||||
return p.pool.pop()
|
||||
}
|
||||
return new(big.Int)
|
||||
}
|
||||
|
||||
// getZero retrieves a big int from the pool, setting it to zero or allocating
|
||||
// a new one if the pool is empty.
|
||||
func (p *intPool) getZero() *big.Int {
|
||||
if p.pool.len() > 0 {
|
||||
return p.pool.pop().SetUint64(0)
|
||||
}
|
||||
return new(big.Int)
|
||||
}
|
||||
|
||||
// put returns an allocated big int to the pool to be later reused by get calls.
|
||||
// Note, the values as saved as is; neither put nor get zeroes the ints out!
|
||||
func (p *intPool) put(is ...*big.Int) {
|
||||
if len(p.pool.data) > poolLimit {
|
||||
return
|
||||
}
|
||||
|
||||
for _, i := range is {
|
||||
// verifyPool is a build flag. Pool verification makes sure the integrity
|
||||
// of the integer pool by comparing values to a default value.
|
||||
if verifyPool {
|
||||
i.Set(checkVal)
|
||||
}
|
||||
|
||||
p.pool.push(i)
|
||||
}
|
||||
}
|
||||
|
||||
// The intPool pool's default capacity
|
||||
const poolDefaultCap = 25
|
||||
|
||||
// intPoolPool manages a pool of intPools.
|
||||
type intPoolPool struct {
|
||||
pools []*intPool
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
var poolOfIntPools = &intPoolPool{
|
||||
pools: make([]*intPool, 0, poolDefaultCap),
|
||||
}
|
||||
|
||||
// get is looking for an available pool to return.
|
||||
func (ipp *intPoolPool) get() *intPool {
|
||||
ipp.lock.Lock()
|
||||
defer ipp.lock.Unlock()
|
||||
|
||||
if len(poolOfIntPools.pools) > 0 {
|
||||
ip := ipp.pools[len(ipp.pools)-1]
|
||||
ipp.pools = ipp.pools[:len(ipp.pools)-1]
|
||||
return ip
|
||||
}
|
||||
return newIntPool()
|
||||
}
|
||||
|
||||
// put a pool that has been allocated with get.
|
||||
func (ipp *intPoolPool) put(ip *intPool) {
|
||||
ipp.lock.Lock()
|
||||
defer ipp.lock.Unlock()
|
||||
|
||||
if len(ipp.pools) < cap(ipp.pools) {
|
||||
ipp.pools = append(ipp.pools, ip)
|
||||
}
|
||||
}
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIntPoolPoolGet(t *testing.T) {
|
||||
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
|
||||
|
||||
nip := poolOfIntPools.get()
|
||||
if nip == nil {
|
||||
t.Fatalf("Invalid pool allocation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntPoolPoolPut(t *testing.T) {
|
||||
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
|
||||
|
||||
nip := poolOfIntPools.get()
|
||||
if len(poolOfIntPools.pools) != 0 {
|
||||
t.Fatalf("Pool got added to list when none should have been")
|
||||
}
|
||||
|
||||
poolOfIntPools.put(nip)
|
||||
if len(poolOfIntPools.pools) == 0 {
|
||||
t.Fatalf("Pool did not get added to list when one should have been")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntPoolPoolReUse(t *testing.T) {
|
||||
poolOfIntPools.pools = make([]*intPool, 0, poolDefaultCap)
|
||||
nip := poolOfIntPools.get()
|
||||
poolOfIntPools.put(nip)
|
||||
poolOfIntPools.get()
|
||||
|
||||
if len(poolOfIntPools.pools) != 0 {
|
||||
t.Fatalf("Invalid number of pools. Got %d, expected %d", len(poolOfIntPools.pools), 0)
|
||||
}
|
||||
}
|
||||
+28
-13
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
executionFunc func(pc *uint64, env *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)
|
||||
executionFunc func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)
|
||||
gasFunc func(params.GasTable, *EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64
|
||||
stackValidationFunc func(*Stack) error
|
||||
memorySizeFunc func(*Stack) *big.Int
|
||||
@@ -33,7 +33,7 @@ type (
|
||||
var errGasUintOverflow = errors.New("gas uint64 overflow")
|
||||
|
||||
type operation struct {
|
||||
// op is the operation function
|
||||
// execute is the operation function
|
||||
execute executionFunc
|
||||
// gasCost is the gas function and returns the gas required for execution
|
||||
gasCost gasFunc
|
||||
@@ -51,17 +51,17 @@ type operation struct {
|
||||
}
|
||||
|
||||
var (
|
||||
frontierInstructionSet = NewFrontierInstructionSet()
|
||||
homesteadInstructionSet = NewHomesteadInstructionSet()
|
||||
byzantiumInstructionSet = NewByzantiumInstructionSet()
|
||||
constantinopleInstructionSet = NewConstantinopleInstructionSet()
|
||||
frontierInstructionSet = newFrontierInstructionSet()
|
||||
homesteadInstructionSet = newHomesteadInstructionSet()
|
||||
byzantiumInstructionSet = newByzantiumInstructionSet()
|
||||
constantinopleInstructionSet = newConstantinopleInstructionSet()
|
||||
)
|
||||
|
||||
// NewConstantinopleInstructionSet returns the frontier, homestead
|
||||
// byzantium and contantinople instructions.
|
||||
func NewConstantinopleInstructionSet() [256]operation {
|
||||
func newConstantinopleInstructionSet() [256]operation {
|
||||
// instructions that can be executed during the byzantium phase.
|
||||
instructionSet := NewByzantiumInstructionSet()
|
||||
instructionSet := newByzantiumInstructionSet()
|
||||
instructionSet[SHL] = operation{
|
||||
execute: opSHL,
|
||||
gasCost: constGasFunc(GasFastestStep),
|
||||
@@ -80,14 +80,29 @@ func NewConstantinopleInstructionSet() [256]operation {
|
||||
validateStack: makeStackFunc(2, 1),
|
||||
valid: true,
|
||||
}
|
||||
instructionSet[EXTCODEHASH] = operation{
|
||||
execute: opExtCodeHash,
|
||||
gasCost: gasExtCodeHash,
|
||||
validateStack: makeStackFunc(1, 1),
|
||||
valid: true,
|
||||
}
|
||||
instructionSet[CREATE2] = operation{
|
||||
execute: opCreate2,
|
||||
gasCost: gasCreate2,
|
||||
validateStack: makeStackFunc(4, 1),
|
||||
memorySize: memoryCreate2,
|
||||
valid: true,
|
||||
writes: true,
|
||||
returns: true,
|
||||
}
|
||||
return instructionSet
|
||||
}
|
||||
|
||||
// NewByzantiumInstructionSet returns the frontier, homestead and
|
||||
// byzantium instructions.
|
||||
func NewByzantiumInstructionSet() [256]operation {
|
||||
func newByzantiumInstructionSet() [256]operation {
|
||||
// instructions that can be executed during the homestead phase.
|
||||
instructionSet := NewHomesteadInstructionSet()
|
||||
instructionSet := newHomesteadInstructionSet()
|
||||
instructionSet[STATICCALL] = operation{
|
||||
execute: opStaticCall,
|
||||
gasCost: gasStaticCall,
|
||||
@@ -123,8 +138,8 @@ func NewByzantiumInstructionSet() [256]operation {
|
||||
|
||||
// NewHomesteadInstructionSet returns the frontier and homestead
|
||||
// instructions that can be executed during the homestead phase.
|
||||
func NewHomesteadInstructionSet() [256]operation {
|
||||
instructionSet := NewFrontierInstructionSet()
|
||||
func newHomesteadInstructionSet() [256]operation {
|
||||
instructionSet := newFrontierInstructionSet()
|
||||
instructionSet[DELEGATECALL] = operation{
|
||||
execute: opDelegateCall,
|
||||
gasCost: gasDelegateCall,
|
||||
@@ -138,7 +153,7 @@ func NewHomesteadInstructionSet() [256]operation {
|
||||
|
||||
// NewFrontierInstructionSet returns the frontier instructions
|
||||
// that can be executed during the frontier phase.
|
||||
func NewFrontierInstructionSet() [256]operation {
|
||||
func newFrontierInstructionSet() [256]operation {
|
||||
return [256]operation{
|
||||
STOP: {
|
||||
execute: opStop,
|
||||
|
||||
+17
-2
@@ -29,11 +29,13 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// Storage represents a contract's storage.
|
||||
type Storage map[common.Hash]common.Hash
|
||||
|
||||
func (self Storage) Copy() Storage {
|
||||
// Copy duplicates the current storage.
|
||||
func (s Storage) Copy() Storage {
|
||||
cpy := make(Storage)
|
||||
for key, value := range self {
|
||||
for key, value := range s {
|
||||
cpy[key] = value
|
||||
}
|
||||
|
||||
@@ -45,6 +47,7 @@ type LogConfig struct {
|
||||
DisableMemory bool // disable memory capture
|
||||
DisableStack bool // disable stack capture
|
||||
DisableStorage bool // disable storage capture
|
||||
Debug bool // print output during capture end
|
||||
Limit int // maximum length of output, but zero means unlimited
|
||||
}
|
||||
|
||||
@@ -75,10 +78,12 @@ type structLogMarshaling struct {
|
||||
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()
|
||||
@@ -123,6 +128,7 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// CaptureStart implements the Tracer interface to initialize the tracing operation.
|
||||
func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
|
||||
return nil
|
||||
}
|
||||
@@ -177,13 +183,22 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
|
||||
return nil
|
||||
}
|
||||
|
||||
// CaptureFault implements the Tracer interface to trace an execution fault
|
||||
// while running an opcode.
|
||||
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CaptureEnd is called after the call finishes to finalize the tracing.
|
||||
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) 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)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ type dummyStateDB struct {
|
||||
|
||||
func TestStoreCapture(t *testing.T) {
|
||||
var (
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{EnableJit: false, ForceJit: false})
|
||||
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
|
||||
logger = NewStructLogger(nil)
|
||||
mem = NewMemory()
|
||||
stack = newstack()
|
||||
|
||||
+33
-13
@@ -16,7 +16,12 @@
|
||||
|
||||
package vm
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
)
|
||||
|
||||
// Memory implements a simple memory model for the ethereum virtual machine.
|
||||
type Memory struct {
|
||||
@@ -24,25 +29,39 @@ type Memory struct {
|
||||
lastGasCost uint64
|
||||
}
|
||||
|
||||
// NewMemory returns a new memory memory model.
|
||||
func NewMemory() *Memory {
|
||||
return &Memory{}
|
||||
}
|
||||
|
||||
// Set sets offset + size to value
|
||||
func (m *Memory) Set(offset, size uint64, value []byte) {
|
||||
// length of store may never be less than offset + size.
|
||||
// The store should be resized PRIOR to setting the memory
|
||||
if size > uint64(len(m.store)) {
|
||||
panic("INVALID memory: store empty")
|
||||
}
|
||||
|
||||
// It's possible the offset is greater than 0 and size equals 0. This is because
|
||||
// the calcMemSize (common.go) could potentially return 0 when size is zero (NO-OP)
|
||||
if size > 0 {
|
||||
// length of store may never be less than offset + size.
|
||||
// The store should be resized PRIOR to setting the memory
|
||||
if offset+size > uint64(len(m.store)) {
|
||||
panic("invalid memory: store empty")
|
||||
}
|
||||
copy(m.store[offset:offset+size], value)
|
||||
}
|
||||
}
|
||||
|
||||
// Set32 sets the 32 bytes starting at offset to the value of val, left-padded with zeroes to
|
||||
// 32 bytes.
|
||||
func (m *Memory) Set32(offset uint64, val *big.Int) {
|
||||
// length of store may never be less than offset + size.
|
||||
// The store should be resized PRIOR to setting the memory
|
||||
if offset+32 > uint64(len(m.store)) {
|
||||
panic("invalid memory: store empty")
|
||||
}
|
||||
// Zero the memory area
|
||||
copy(m.store[offset:offset+32], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
// Fill in relevant bits
|
||||
math.ReadBits(val, m.store[offset:offset+32])
|
||||
}
|
||||
|
||||
// Resize resizes the memory to size
|
||||
func (m *Memory) Resize(size uint64) {
|
||||
if uint64(m.Len()) < size {
|
||||
@@ -51,14 +70,14 @@ func (m *Memory) Resize(size uint64) {
|
||||
}
|
||||
|
||||
// Get returns offset + size as a new slice
|
||||
func (self *Memory) Get(offset, size int64) (cpy []byte) {
|
||||
func (m *Memory) Get(offset, size int64) (cpy []byte) {
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(self.store) > int(offset) {
|
||||
if len(m.store) > int(offset) {
|
||||
cpy = make([]byte, size)
|
||||
copy(cpy, self.store[offset:offset+size])
|
||||
copy(cpy, m.store[offset:offset+size])
|
||||
|
||||
return
|
||||
}
|
||||
@@ -67,13 +86,13 @@ func (self *Memory) Get(offset, size int64) (cpy []byte) {
|
||||
}
|
||||
|
||||
// GetPtr returns the offset + size
|
||||
func (self *Memory) GetPtr(offset, size int64) []byte {
|
||||
func (m *Memory) GetPtr(offset, size int64) []byte {
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(self.store) > int(offset) {
|
||||
return self.store[offset : offset+size]
|
||||
if len(m.store) > int(offset) {
|
||||
return m.store[offset : offset+size]
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -89,6 +108,7 @@ func (m *Memory) Data() []byte {
|
||||
return m.store
|
||||
}
|
||||
|
||||
// Print dumps the content of the memory.
|
||||
func (m *Memory) Print() {
|
||||
fmt.Printf("### mem %d bytes ###\n", len(m.store))
|
||||
if len(m.store) > 0 {
|
||||
|
||||
+4
-6
@@ -58,6 +58,10 @@ func memoryCreate(stack *Stack) *big.Int {
|
||||
return calcMemSize(stack.Back(1), stack.Back(2))
|
||||
}
|
||||
|
||||
func memoryCreate2(stack *Stack) *big.Int {
|
||||
return calcMemSize(stack.Back(1), stack.Back(2))
|
||||
}
|
||||
|
||||
func memoryCall(stack *Stack) *big.Int {
|
||||
x := calcMemSize(stack.Back(5), stack.Back(6))
|
||||
y := calcMemSize(stack.Back(3), stack.Back(4))
|
||||
@@ -65,12 +69,6 @@ func memoryCall(stack *Stack) *big.Int {
|
||||
return math.BigMax(x, y)
|
||||
}
|
||||
|
||||
func memoryCallCode(stack *Stack) *big.Int {
|
||||
x := calcMemSize(stack.Back(5), stack.Back(6))
|
||||
y := calcMemSize(stack.Back(3), stack.Back(4))
|
||||
|
||||
return math.BigMax(x, y)
|
||||
}
|
||||
func memoryDelegateCall(stack *Stack) *big.Int {
|
||||
x := calcMemSize(stack.Back(4), stack.Back(5))
|
||||
y := calcMemSize(stack.Back(2), stack.Back(3))
|
||||
|
||||
+30
-19
@@ -23,6 +23,7 @@ import (
|
||||
// OpCode is an EVM opcode
|
||||
type OpCode byte
|
||||
|
||||
// IsPush specifies if an opcode is a PUSH opcode.
|
||||
func (op OpCode) IsPush() bool {
|
||||
switch op {
|
||||
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
|
||||
@@ -31,12 +32,13 @@ 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 (
|
||||
// 0x0 range - arithmetic ops
|
||||
STOP OpCode = iota
|
||||
ADD
|
||||
MUL
|
||||
@@ -51,6 +53,7 @@ const (
|
||||
SIGNEXTEND
|
||||
)
|
||||
|
||||
// 0x10 range - comparison ops.
|
||||
const (
|
||||
LT OpCode = iota + 0x10
|
||||
GT
|
||||
@@ -70,8 +73,8 @@ const (
|
||||
SHA3 = 0x20
|
||||
)
|
||||
|
||||
// 0x30 range - closure state.
|
||||
const (
|
||||
// 0x30 range - closure state
|
||||
ADDRESS OpCode = 0x30 + iota
|
||||
BALANCE
|
||||
ORIGIN
|
||||
@@ -87,10 +90,11 @@ const (
|
||||
EXTCODECOPY
|
||||
RETURNDATASIZE
|
||||
RETURNDATACOPY
|
||||
EXTCODEHASH
|
||||
)
|
||||
|
||||
// 0x40 range - block operations.
|
||||
const (
|
||||
// 0x40 range - block operations
|
||||
BLOCKHASH OpCode = 0x40 + iota
|
||||
COINBASE
|
||||
TIMESTAMP
|
||||
@@ -99,8 +103,8 @@ const (
|
||||
GASLIMIT
|
||||
)
|
||||
|
||||
// 0x50 range - 'storage' and execution.
|
||||
const (
|
||||
// 0x50 range - 'storage' and execution
|
||||
POP OpCode = 0x50 + iota
|
||||
MLOAD
|
||||
MSTORE
|
||||
@@ -115,8 +119,8 @@ const (
|
||||
JUMPDEST
|
||||
)
|
||||
|
||||
// 0x60 range.
|
||||
const (
|
||||
// 0x60 range
|
||||
PUSH1 OpCode = 0x60 + iota
|
||||
PUSH2
|
||||
PUSH3
|
||||
@@ -183,6 +187,7 @@ const (
|
||||
SWAP16
|
||||
)
|
||||
|
||||
// 0xa0 range - logging ops.
|
||||
const (
|
||||
LOG0 OpCode = 0xa0 + iota
|
||||
LOG1
|
||||
@@ -191,29 +196,30 @@ const (
|
||||
LOG4
|
||||
)
|
||||
|
||||
// unofficial opcodes used for parsing
|
||||
// unofficial opcodes used for parsing.
|
||||
const (
|
||||
PUSH OpCode = 0xb0 + iota
|
||||
DUP
|
||||
SWAP
|
||||
)
|
||||
|
||||
// 0xf0 range - closures.
|
||||
const (
|
||||
// 0xf0 range - closures
|
||||
CREATE OpCode = 0xf0 + iota
|
||||
CALL
|
||||
CALLCODE
|
||||
RETURN
|
||||
DELEGATECALL
|
||||
CREATE2
|
||||
STATICCALL = 0xfa
|
||||
|
||||
REVERT = 0xfd
|
||||
SELFDESTRUCT = 0xff
|
||||
)
|
||||
|
||||
// Since the opcodes aren't all in order we can't use a regular slice
|
||||
// Since the opcodes aren't all in order we can't use a regular slice.
|
||||
var opCodeToString = map[OpCode]string{
|
||||
// 0x0 range - arithmetic ops
|
||||
// 0x0 range - arithmetic ops.
|
||||
STOP: "STOP",
|
||||
ADD: "ADD",
|
||||
MUL: "MUL",
|
||||
@@ -232,7 +238,7 @@ var opCodeToString = map[OpCode]string{
|
||||
ISZERO: "ISZERO",
|
||||
SIGNEXTEND: "SIGNEXTEND",
|
||||
|
||||
// 0x10 range - bit ops
|
||||
// 0x10 range - bit ops.
|
||||
AND: "AND",
|
||||
OR: "OR",
|
||||
XOR: "XOR",
|
||||
@@ -243,10 +249,10 @@ var opCodeToString = map[OpCode]string{
|
||||
ADDMOD: "ADDMOD",
|
||||
MULMOD: "MULMOD",
|
||||
|
||||
// 0x20 range - crypto
|
||||
// 0x20 range - crypto.
|
||||
SHA3: "SHA3",
|
||||
|
||||
// 0x30 range - closure state
|
||||
// 0x30 range - closure state.
|
||||
ADDRESS: "ADDRESS",
|
||||
BALANCE: "BALANCE",
|
||||
ORIGIN: "ORIGIN",
|
||||
@@ -262,8 +268,9 @@ var opCodeToString = map[OpCode]string{
|
||||
EXTCODECOPY: "EXTCODECOPY",
|
||||
RETURNDATASIZE: "RETURNDATASIZE",
|
||||
RETURNDATACOPY: "RETURNDATACOPY",
|
||||
EXTCODEHASH: "EXTCODEHASH",
|
||||
|
||||
// 0x40 range - block operations
|
||||
// 0x40 range - block operations.
|
||||
BLOCKHASH: "BLOCKHASH",
|
||||
COINBASE: "COINBASE",
|
||||
TIMESTAMP: "TIMESTAMP",
|
||||
@@ -271,7 +278,7 @@ var opCodeToString = map[OpCode]string{
|
||||
DIFFICULTY: "DIFFICULTY",
|
||||
GASLIMIT: "GASLIMIT",
|
||||
|
||||
// 0x50 range - 'storage' and execution
|
||||
// 0x50 range - 'storage' and execution.
|
||||
POP: "POP",
|
||||
//DUP: "DUP",
|
||||
//SWAP: "SWAP",
|
||||
@@ -287,7 +294,7 @@ var opCodeToString = map[OpCode]string{
|
||||
GAS: "GAS",
|
||||
JUMPDEST: "JUMPDEST",
|
||||
|
||||
// 0x60 range - push
|
||||
// 0x60 range - push.
|
||||
PUSH1: "PUSH1",
|
||||
PUSH2: "PUSH2",
|
||||
PUSH3: "PUSH3",
|
||||
@@ -360,12 +367,13 @@ var opCodeToString = map[OpCode]string{
|
||||
LOG3: "LOG3",
|
||||
LOG4: "LOG4",
|
||||
|
||||
// 0xf0 range
|
||||
// 0xf0 range.
|
||||
CREATE: "CREATE",
|
||||
CALL: "CALL",
|
||||
RETURN: "RETURN",
|
||||
CALLCODE: "CALLCODE",
|
||||
DELEGATECALL: "DELEGATECALL",
|
||||
CREATE2: "CREATE2",
|
||||
STATICCALL: "STATICCALL",
|
||||
REVERT: "REVERT",
|
||||
SELFDESTRUCT: "SELFDESTRUCT",
|
||||
@@ -375,10 +383,10 @@ var opCodeToString = map[OpCode]string{
|
||||
SWAP: "SWAP",
|
||||
}
|
||||
|
||||
func (o OpCode) String() string {
|
||||
str := opCodeToString[o]
|
||||
func (op OpCode) String() string {
|
||||
str := opCodeToString[op]
|
||||
if len(str) == 0 {
|
||||
return fmt.Sprintf("Missing opcode 0x%x", int(o))
|
||||
return fmt.Sprintf("Missing opcode 0x%x", int(op))
|
||||
}
|
||||
|
||||
return str
|
||||
@@ -429,6 +437,7 @@ var stringToOp = map[string]OpCode{
|
||||
"EXTCODECOPY": EXTCODECOPY,
|
||||
"RETURNDATASIZE": RETURNDATASIZE,
|
||||
"RETURNDATACOPY": RETURNDATACOPY,
|
||||
"EXTCODEHASH": EXTCODEHASH,
|
||||
"BLOCKHASH": BLOCKHASH,
|
||||
"COINBASE": COINBASE,
|
||||
"TIMESTAMP": TIMESTAMP,
|
||||
@@ -517,6 +526,7 @@ var stringToOp = map[string]OpCode{
|
||||
"LOG3": LOG3,
|
||||
"LOG4": LOG4,
|
||||
"CREATE": CREATE,
|
||||
"CREATE2": CREATE2,
|
||||
"CALL": CALL,
|
||||
"RETURN": RETURN,
|
||||
"CALLCODE": CALLCODE,
|
||||
@@ -524,6 +534,7 @@ var stringToOp = map[string]OpCode{
|
||||
"SELFDESTRUCT": SELFDESTRUCT,
|
||||
}
|
||||
|
||||
// StringToOp finds the opcode whose name is stored in `str`.
|
||||
func StringToOp(str string) OpCode {
|
||||
return stringToOp[str]
|
||||
}
|
||||
|
||||
+6
-10
@@ -41,7 +41,6 @@ type Config struct {
|
||||
GasLimit uint64
|
||||
GasPrice *big.Int
|
||||
Value *big.Int
|
||||
DisableJit bool // "disable" so it's enabled by default
|
||||
Debug bool
|
||||
EVMConfig vm.Config
|
||||
|
||||
@@ -53,7 +52,7 @@ type Config struct {
|
||||
func setDefaults(cfg *Config) {
|
||||
if cfg.ChainConfig == nil {
|
||||
cfg.ChainConfig = ¶ms.ChainConfig{
|
||||
ChainId: big.NewInt(1),
|
||||
ChainID: big.NewInt(1),
|
||||
HomesteadBlock: new(big.Int),
|
||||
DAOForkBlock: new(big.Int),
|
||||
DAOForkSupport: false,
|
||||
@@ -92,8 +91,7 @@ func setDefaults(cfg *Config) {
|
||||
// It returns the EVM's return value, the new state and an error if it failed.
|
||||
//
|
||||
// Executes sets up a in memory, temporarily, environment for the execution of
|
||||
// the given code. It enabled the JIT by default and make sure that it's restored
|
||||
// to it's original state afterwards.
|
||||
// the given code. It makes sure that it's restored to it's original state afterwards.
|
||||
func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
||||
if cfg == nil {
|
||||
cfg = new(Config)
|
||||
@@ -101,11 +99,10 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
||||
setDefaults(cfg)
|
||||
|
||||
if cfg.State == nil {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db))
|
||||
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
}
|
||||
var (
|
||||
address = common.StringToAddress("contract")
|
||||
address = common.BytesToAddress([]byte("contract"))
|
||||
vmenv = NewEnv(cfg)
|
||||
sender = vm.AccountRef(cfg.Origin)
|
||||
)
|
||||
@@ -115,7 +112,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
||||
// Call the code with the given configuration.
|
||||
ret, _, err := vmenv.Call(
|
||||
sender,
|
||||
common.StringToAddress("contract"),
|
||||
common.BytesToAddress([]byte("contract")),
|
||||
input,
|
||||
cfg.GasLimit,
|
||||
cfg.Value,
|
||||
@@ -132,8 +129,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
|
||||
setDefaults(cfg)
|
||||
|
||||
if cfg.State == nil {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db))
|
||||
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
}
|
||||
var (
|
||||
vmenv = NewEnv(cfg)
|
||||
|
||||
+1
-2
@@ -94,8 +94,7 @@ func TestExecute(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCall(t *testing.T) {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
state, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
state, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||
address := common.HexToAddress("0x0a")
|
||||
state.SetCode(address, []byte{
|
||||
byte(vm.PUSH1), 10,
|
||||
|
||||
+3
-1
@@ -21,7 +21,7 @@ import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// stack is an object for basic stack operations. Items popped to the stack are
|
||||
// Stack is an object for basic stack operations. Items popped to the stack are
|
||||
// expected to be changed and modified. stack does not take care of adding newly
|
||||
// initialised objects.
|
||||
type Stack struct {
|
||||
@@ -32,6 +32,7 @@ func newstack() *Stack {
|
||||
return &Stack{data: make([]*big.Int, 0, 1024)}
|
||||
}
|
||||
|
||||
// Data returns the underlying big.Int array.
|
||||
func (st *Stack) Data() []*big.Int {
|
||||
return st.data
|
||||
}
|
||||
@@ -80,6 +81,7 @@ func (st *Stack) require(n int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Print dumps the content of the stack
|
||||
func (st *Stack) Print() {
|
||||
fmt.Println("### stack ###")
|
||||
if len(st.data) > 0 {
|
||||
|
||||
-389
@@ -1,389 +0,0 @@
|
||||
// Copyright 2015 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/>.
|
||||
|
||||
// +build evmjit
|
||||
|
||||
package vm
|
||||
|
||||
/*
|
||||
|
||||
void* evmjit_create();
|
||||
int evmjit_run(void* _jit, void* _data, void* _env);
|
||||
void evmjit_destroy(void* _jit);
|
||||
|
||||
// Shared library evmjit (e.g. libevmjit.so) is expected to be installed in /usr/local/lib
|
||||
// More: https://github.com/ethereum/evmjit
|
||||
#cgo LDFLAGS: -levmjit
|
||||
*/
|
||||
import "C"
|
||||
|
||||
/*
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
type JitVm struct {
|
||||
env EVM
|
||||
me ContextRef
|
||||
callerAddr []byte
|
||||
price *big.Int
|
||||
data RuntimeData
|
||||
}
|
||||
|
||||
type i256 [32]byte
|
||||
|
||||
type RuntimeData struct {
|
||||
gas int64
|
||||
gasPrice int64
|
||||
callData *byte
|
||||
callDataSize uint64
|
||||
address i256
|
||||
caller i256
|
||||
origin i256
|
||||
callValue i256
|
||||
coinBase i256
|
||||
difficulty i256
|
||||
gasLimit i256
|
||||
number uint64
|
||||
timestamp int64
|
||||
code *byte
|
||||
codeSize uint64
|
||||
codeHash i256
|
||||
}
|
||||
|
||||
func hash2llvm(h []byte) i256 {
|
||||
var m i256
|
||||
copy(m[len(m)-len(h):], h) // right aligned copy
|
||||
return m
|
||||
}
|
||||
|
||||
func llvm2hash(m *i256) []byte {
|
||||
return C.GoBytes(unsafe.Pointer(m), C.int(len(m)))
|
||||
}
|
||||
|
||||
func llvm2hashRef(m *i256) []byte {
|
||||
return (*[1 << 30]byte)(unsafe.Pointer(m))[:len(m):len(m)]
|
||||
}
|
||||
|
||||
func address2llvm(addr []byte) i256 {
|
||||
n := hash2llvm(addr)
|
||||
bswap(&n)
|
||||
return n
|
||||
}
|
||||
|
||||
// bswap swap bytes of the 256-bit integer on LLVM side
|
||||
// TODO: Do not change memory on LLVM side, that can conflict with memory access optimizations
|
||||
func bswap(m *i256) *i256 {
|
||||
for i, l := 0, len(m); i < l/2; i++ {
|
||||
m[i], m[l-i-1] = m[l-i-1], m[i]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func trim(m []byte) []byte {
|
||||
skip := 0
|
||||
for i := 0; i < len(m); i++ {
|
||||
if m[i] == 0 {
|
||||
skip++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return m[skip:]
|
||||
}
|
||||
|
||||
func getDataPtr(m []byte) *byte {
|
||||
var p *byte
|
||||
if len(m) > 0 {
|
||||
p = &m[0]
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func big2llvm(n *big.Int) i256 {
|
||||
m := hash2llvm(n.Bytes())
|
||||
bswap(&m)
|
||||
return m
|
||||
}
|
||||
|
||||
func llvm2big(m *i256) *big.Int {
|
||||
n := big.NewInt(0)
|
||||
for i := 0; i < len(m); i++ {
|
||||
b := big.NewInt(int64(m[i]))
|
||||
b.Lsh(b, uint(i)*8)
|
||||
n.Add(n, b)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// llvm2bytesRef creates a []byte slice that references byte buffer on LLVM side (as of that not controller by GC)
|
||||
// User must ensure that referenced memory is available to Go until the data is copied or not needed any more
|
||||
func llvm2bytesRef(data *byte, length uint64) []byte {
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
if data == nil {
|
||||
panic("Unexpected nil data pointer")
|
||||
}
|
||||
return (*[1 << 30]byte)(unsafe.Pointer(data))[:length:length]
|
||||
}
|
||||
|
||||
func untested(condition bool, message string) {
|
||||
if condition {
|
||||
panic("Condition `" + message + "` tested. Remove assert.")
|
||||
}
|
||||
}
|
||||
|
||||
func assert(condition bool, message string) {
|
||||
if !condition {
|
||||
panic("Assert `" + message + "` failed!")
|
||||
}
|
||||
}
|
||||
|
||||
func NewJitVm(env EVM) *JitVm {
|
||||
return &JitVm{env: env}
|
||||
}
|
||||
|
||||
func (self *JitVm) Run(me, caller ContextRef, code []byte, value, gas, price *big.Int, callData []byte) (ret []byte, err error) {
|
||||
// TODO: depth is increased but never checked by VM. VM should not know about it at all.
|
||||
self.env.SetDepth(self.env.Depth() + 1)
|
||||
|
||||
// TODO: Move it to Env.Call() or sth
|
||||
if Precompiled[string(me.Address())] != nil {
|
||||
// if it's address of precompiled contract
|
||||
// fallback to standard VM
|
||||
stdVm := New(self.env)
|
||||
return stdVm.Run(me, caller, code, value, gas, price, callData)
|
||||
}
|
||||
|
||||
if self.me != nil {
|
||||
panic("JitVm.Run() can be called only once per JitVm instance")
|
||||
}
|
||||
|
||||
self.me = me
|
||||
self.callerAddr = caller.Address()
|
||||
self.price = price
|
||||
|
||||
self.data.gas = gas.Int64()
|
||||
self.data.gasPrice = price.Int64()
|
||||
self.data.callData = getDataPtr(callData)
|
||||
self.data.callDataSize = uint64(len(callData))
|
||||
self.data.address = address2llvm(self.me.Address())
|
||||
self.data.caller = address2llvm(caller.Address())
|
||||
self.data.origin = address2llvm(self.env.Origin())
|
||||
self.data.callValue = big2llvm(value)
|
||||
self.data.coinBase = address2llvm(self.env.Coinbase())
|
||||
self.data.difficulty = big2llvm(self.env.Difficulty())
|
||||
self.data.gasLimit = big2llvm(self.env.GasLimit())
|
||||
self.data.number = self.env.BlockNumber().Uint64()
|
||||
self.data.timestamp = self.env.Time()
|
||||
self.data.code = getDataPtr(code)
|
||||
self.data.codeSize = uint64(len(code))
|
||||
self.data.codeHash = hash2llvm(crypto.Keccak256(code)) // TODO: Get already computed hash?
|
||||
|
||||
jit := C.evmjit_create()
|
||||
retCode := C.evmjit_run(jit, unsafe.Pointer(&self.data), unsafe.Pointer(self))
|
||||
|
||||
if retCode < 0 {
|
||||
err = errors.New("OOG from JIT")
|
||||
gas.SetInt64(0) // Set gas to 0, JIT does not bother
|
||||
} else {
|
||||
gas.SetInt64(self.data.gas)
|
||||
if retCode == 1 { // RETURN
|
||||
ret = C.GoBytes(unsafe.Pointer(self.data.callData), C.int(self.data.callDataSize))
|
||||
} else if retCode == 2 { // SUICIDE
|
||||
// TODO: Suicide support logic should be moved to Env to be shared by VM implementations
|
||||
state := self.Env().State()
|
||||
receiverAddr := llvm2hashRef(bswap(&self.data.address))
|
||||
receiver := state.GetOrNewStateObject(receiverAddr)
|
||||
balance := state.GetBalance(me.Address())
|
||||
receiver.AddBalance(balance)
|
||||
state.Delete(me.Address())
|
||||
}
|
||||
}
|
||||
|
||||
C.evmjit_destroy(jit)
|
||||
return
|
||||
}
|
||||
|
||||
func (self *JitVm) Printf(format string, v ...interface{}) VirtualMachine {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *JitVm) Endl() VirtualMachine {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *JitVm) Env() EVM {
|
||||
return self.env
|
||||
}
|
||||
|
||||
//export env_sha3
|
||||
func env_sha3(dataPtr *byte, length uint64, resultPtr unsafe.Pointer) {
|
||||
data := llvm2bytesRef(dataPtr, length)
|
||||
hash := crypto.Keccak256(data)
|
||||
result := (*i256)(resultPtr)
|
||||
*result = hash2llvm(hash)
|
||||
}
|
||||
|
||||
//export env_sstore
|
||||
func env_sstore(vmPtr unsafe.Pointer, indexPtr unsafe.Pointer, valuePtr unsafe.Pointer) {
|
||||
vm := (*JitVm)(vmPtr)
|
||||
index := llvm2hash(bswap((*i256)(indexPtr)))
|
||||
value := llvm2hash(bswap((*i256)(valuePtr)))
|
||||
value = trim(value)
|
||||
if len(value) == 0 {
|
||||
prevValue := vm.env.State().GetState(vm.me.Address(), index)
|
||||
if len(prevValue) != 0 {
|
||||
vm.Env().State().Refund(vm.callerAddr, GasSStoreRefund)
|
||||
}
|
||||
}
|
||||
|
||||
vm.env.State().SetState(vm.me.Address(), index, value)
|
||||
}
|
||||
|
||||
//export env_sload
|
||||
func env_sload(vmPtr unsafe.Pointer, indexPtr unsafe.Pointer, resultPtr unsafe.Pointer) {
|
||||
vm := (*JitVm)(vmPtr)
|
||||
index := llvm2hash(bswap((*i256)(indexPtr)))
|
||||
value := vm.env.State().GetState(vm.me.Address(), index)
|
||||
result := (*i256)(resultPtr)
|
||||
*result = hash2llvm(value)
|
||||
bswap(result)
|
||||
}
|
||||
|
||||
//export env_balance
|
||||
func env_balance(_vm unsafe.Pointer, _addr unsafe.Pointer, _result unsafe.Pointer) {
|
||||
vm := (*JitVm)(_vm)
|
||||
addr := llvm2hash((*i256)(_addr))
|
||||
balance := vm.Env().State().GetBalance(addr)
|
||||
result := (*i256)(_result)
|
||||
*result = big2llvm(balance)
|
||||
}
|
||||
|
||||
//export env_blockhash
|
||||
func env_blockhash(_vm unsafe.Pointer, _number unsafe.Pointer, _result unsafe.Pointer) {
|
||||
vm := (*JitVm)(_vm)
|
||||
number := llvm2big((*i256)(_number))
|
||||
result := (*i256)(_result)
|
||||
|
||||
currNumber := vm.Env().BlockNumber()
|
||||
limit := big.NewInt(0).Sub(currNumber, big.NewInt(256))
|
||||
if number.Cmp(limit) >= 0 && number.Cmp(currNumber) < 0 {
|
||||
hash := vm.Env().GetHash(uint64(number.Int64()))
|
||||
*result = hash2llvm(hash)
|
||||
} else {
|
||||
*result = i256{}
|
||||
}
|
||||
}
|
||||
|
||||
//export env_call
|
||||
func env_call(_vm unsafe.Pointer, _gas *int64, _receiveAddr unsafe.Pointer, _value unsafe.Pointer, inDataPtr unsafe.Pointer, inDataLen uint64, outDataPtr *byte, outDataLen uint64, _codeAddr unsafe.Pointer) bool {
|
||||
vm := (*JitVm)(_vm)
|
||||
|
||||
//fmt.Printf("env_call (depth %d)\n", vm.Env().Depth())
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf("Recovered in env_call (depth %d, out %p %d): %s\n", vm.Env().Depth(), outDataPtr, outDataLen, r)
|
||||
}
|
||||
}()
|
||||
|
||||
balance := vm.Env().State().GetBalance(vm.me.Address())
|
||||
value := llvm2big((*i256)(_value))
|
||||
|
||||
if balance.Cmp(value) >= 0 {
|
||||
receiveAddr := llvm2hash((*i256)(_receiveAddr))
|
||||
inData := C.GoBytes(inDataPtr, C.int(inDataLen))
|
||||
outData := llvm2bytesRef(outDataPtr, outDataLen)
|
||||
codeAddr := llvm2hash((*i256)(_codeAddr))
|
||||
gas := big.NewInt(*_gas)
|
||||
var out []byte
|
||||
var err error
|
||||
if bytes.Equal(codeAddr, receiveAddr) {
|
||||
out, err = vm.env.Call(vm.me, codeAddr, inData, gas, vm.price, value)
|
||||
} else {
|
||||
out, err = vm.env.CallCode(vm.me, codeAddr, inData, gas, vm.price, value)
|
||||
}
|
||||
*_gas = gas.Int64()
|
||||
if err == nil {
|
||||
copy(outData, out)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
//export env_create
|
||||
func env_create(_vm unsafe.Pointer, _gas *int64, _value unsafe.Pointer, initDataPtr unsafe.Pointer, initDataLen uint64, _result unsafe.Pointer) {
|
||||
vm := (*JitVm)(_vm)
|
||||
|
||||
value := llvm2big((*i256)(_value))
|
||||
initData := C.GoBytes(initDataPtr, C.int(initDataLen)) // TODO: Unnecessary if low balance
|
||||
result := (*i256)(_result)
|
||||
*result = i256{}
|
||||
|
||||
gas := big.NewInt(*_gas)
|
||||
ret, suberr, ref := vm.env.Create(vm.me, nil, initData, gas, vm.price, value)
|
||||
if suberr == nil {
|
||||
dataGas := big.NewInt(int64(len(ret))) // TODO: Not the best design. env.Create can do it, it has the reference to gas counter
|
||||
dataGas.Mul(dataGas, params.CreateDataGas)
|
||||
gas.Sub(gas, dataGas)
|
||||
*result = hash2llvm(ref.Address())
|
||||
}
|
||||
*_gas = gas.Int64()
|
||||
}
|
||||
|
||||
//export env_log
|
||||
func env_log(_vm unsafe.Pointer, dataPtr unsafe.Pointer, dataLen uint64, _topic1 unsafe.Pointer, _topic2 unsafe.Pointer, _topic3 unsafe.Pointer, _topic4 unsafe.Pointer) {
|
||||
vm := (*JitVm)(_vm)
|
||||
|
||||
data := C.GoBytes(dataPtr, C.int(dataLen))
|
||||
|
||||
topics := make([][]byte, 0, 4)
|
||||
if _topic1 != nil {
|
||||
topics = append(topics, llvm2hash((*i256)(_topic1)))
|
||||
}
|
||||
if _topic2 != nil {
|
||||
topics = append(topics, llvm2hash((*i256)(_topic2)))
|
||||
}
|
||||
if _topic3 != nil {
|
||||
topics = append(topics, llvm2hash((*i256)(_topic3)))
|
||||
}
|
||||
if _topic4 != nil {
|
||||
topics = append(topics, llvm2hash((*i256)(_topic4)))
|
||||
}
|
||||
|
||||
vm.Env().AddLog(state.NewLog(vm.me.Address(), topics, data, vm.env.BlockNumber().Uint64()))
|
||||
}
|
||||
|
||||
//export env_extcode
|
||||
func env_extcode(_vm unsafe.Pointer, _addr unsafe.Pointer, o_size *uint64) *byte {
|
||||
vm := (*JitVm)(_vm)
|
||||
addr := llvm2hash((*i256)(_addr))
|
||||
code := vm.Env().State().GetCode(addr)
|
||||
*o_size = uint64(len(code))
|
||||
return getDataPtr(code)
|
||||
}*/
|
||||
Reference in New Issue
Block a user