aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorJeffrey Wilcke <geffobscura@gmail.com>2015-11-03 18:47:07 +0800
committerJeffrey Wilcke <geffobscura@gmail.com>2015-11-03 18:47:07 +0800
commite5532154a50114d5ffb1ffd850b746cab00cb899 (patch)
tree0042cc997ccf4166b9b52464339d52d37d7a8ad6 /core
parent9666db2a442887ccf8ec2d81f5e2fedc1a3a3d3e (diff)
parentf75becc264f8bde0f58391fc226243d03e78aa7b (diff)
downloadgo-tangerine-e5532154a50114d5ffb1ffd850b746cab00cb899.tar
go-tangerine-e5532154a50114d5ffb1ffd850b746cab00cb899.tar.gz
go-tangerine-e5532154a50114d5ffb1ffd850b746cab00cb899.tar.bz2
go-tangerine-e5532154a50114d5ffb1ffd850b746cab00cb899.tar.lz
go-tangerine-e5532154a50114d5ffb1ffd850b746cab00cb899.tar.xz
go-tangerine-e5532154a50114d5ffb1ffd850b746cab00cb899.tar.zst
go-tangerine-e5532154a50114d5ffb1ffd850b746cab00cb899.zip
Merge branch 'release/1.3.0'
Conflicts: VERSION cmd/geth/main.go
Diffstat (limited to 'core')
-rw-r--r--core/bench_test.go4
-rw-r--r--core/block_processor.go129
-rw-r--r--core/block_processor_test.go29
-rw-r--r--core/blockchain.go1273
-rw-r--r--core/blockchain_test.go954
-rw-r--r--core/chain_makers.go111
-rw-r--r--core/chain_makers_test.go6
-rw-r--r--core/chain_manager.go847
-rw-r--r--core/chain_manager_test.go652
-rw-r--r--core/chain_pow_test.go10
-rw-r--r--core/chain_util.go68
-rw-r--r--core/chain_util_test.go137
-rw-r--r--core/error.go15
-rw-r--r--core/events.go10
-rw-r--r--core/execution.go107
-rw-r--r--core/filter.go212
-rw-r--r--core/genesis.go47
-rw-r--r--core/helper_test.go6
-rw-r--r--core/manager.go2
-rw-r--r--core/state/errors.go39
-rw-r--r--core/state/managed_state_test.go2
-rw-r--r--core/state/state_object.go56
-rw-r--r--core/state/state_test.go14
-rw-r--r--core/state/statedb.go148
-rw-r--r--core/state/sync.go70
-rw-r--r--core/state/sync_test.go238
-rw-r--r--core/state_transition.go117
-rw-r--r--core/transaction_pool.go53
-rw-r--r--core/transaction_pool_test.go53
-rw-r--r--core/transaction_util.go55
-rw-r--r--core/types/block.go31
-rw-r--r--core/types/bloom9.go47
-rw-r--r--core/types/bloom9_test.go34
-rw-r--r--core/types/common.go39
-rw-r--r--core/types/derive_sha.go15
-rw-r--r--core/types/receipt.go128
-rw-r--r--core/vm/asm.go2
-rw-r--r--core/vm/common.go55
-rw-r--r--core/vm/contract.go (renamed from core/vm/context.go)52
-rw-r--r--core/vm/contracts.go10
-rw-r--r--core/vm/doc.go35
-rw-r--r--core/vm/environment.go87
-rw-r--r--core/vm/gas.go2
-rw-r--r--core/vm/instructions.go323
-rw-r--r--core/vm/jit.go167
-rw-r--r--core/vm/jit_optimiser.go107
-rw-r--r--core/vm/jit_test.go139
-rw-r--r--core/vm/jit_util.go68
-rw-r--r--core/vm/jit_util_test.go84
-rw-r--r--core/vm/jump_table.go143
-rw-r--r--core/vm/log.go (renamed from core/state/log.go)53
-rw-r--r--core/vm/logger.go1
-rw-r--r--core/vm/memory.go7
-rw-r--r--core/vm/opcodes.go28
-rw-r--r--core/vm/segments.go44
-rw-r--r--core/vm/settings.go25
-rw-r--r--core/vm/stack.go3
-rw-r--r--core/vm/virtual_machine.go3
-rw-r--r--core/vm/vm.go690
-rw-r--r--core/vm/vm_jit.go3
-rw-r--r--core/vm_env.go46
61 files changed, 4695 insertions, 3240 deletions
diff --git a/core/bench_test.go b/core/bench_test.go
index def4f0d2a..b5eb51803 100644
--- a/core/bench_test.go
+++ b/core/bench_test.go
@@ -163,12 +163,12 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
// Generate a chain of b.N blocks using the supplied block
// generator function.
genesis := WriteGenesisBlockForTesting(db, GenesisAccount{benchRootAddr, benchRootFunds})
- chain := GenerateChain(genesis, db, b.N, gen)
+ chain, _ := GenerateChain(genesis, db, b.N, gen)
// Time the insertion of the new chain.
// State and blocks are stored in the same DB.
evmux := new(event.TypeMux)
- chainman, _ := NewChainManager(db, FakePow{}, evmux)
+ chainman, _ := NewBlockChain(db, FakePow{}, evmux)
chainman.SetProcessor(NewBlockProcessor(db, FakePow{}, chainman, evmux))
defer chainman.Stop()
b.ReportAllocs()
diff --git a/core/block_processor.go b/core/block_processor.go
index 238b2db95..e7b2f63e5 100644
--- a/core/block_processor.go
+++ b/core/block_processor.go
@@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
@@ -46,7 +47,7 @@ type BlockProcessor struct {
// Mutex for locking the block processor. Blocks can only be handled one at a time
mutex sync.Mutex
// Canonical block chain
- bc *ChainManager
+ bc *BlockChain
// non-persistent key/value memory storage
mem map[string]*big.Int
// Proof of work used for validating
@@ -57,32 +58,49 @@ type BlockProcessor struct {
eventMux *event.TypeMux
}
-// TODO: type GasPool big.Int
-//
-// GasPool is implemented by state.StateObject. This is a historical
-// coincidence. Gas tracking should move out of StateObject.
-
// GasPool tracks the amount of gas available during
// execution of the transactions in a block.
-type GasPool interface {
- AddGas(gas, price *big.Int)
- SubGas(gas, price *big.Int) error
+// The zero value is a pool with zero gas available.
+type GasPool big.Int
+
+// AddGas makes gas available for execution.
+func (gp *GasPool) AddGas(amount *big.Int) *GasPool {
+ i := (*big.Int)(gp)
+ i.Add(i, amount)
+ return gp
+}
+
+// SubGas deducts the given amount from the pool if enough gas is
+// available and returns an error otherwise.
+func (gp *GasPool) SubGas(amount *big.Int) error {
+ i := (*big.Int)(gp)
+ if i.Cmp(amount) < 0 {
+ return &GasLimitErr{Have: new(big.Int).Set(i), Want: amount}
+ }
+ i.Sub(i, amount)
+ return nil
+}
+
+func (gp *GasPool) String() string {
+ return (*big.Int)(gp).String()
}
-func NewBlockProcessor(db ethdb.Database, pow pow.PoW, chainManager *ChainManager, eventMux *event.TypeMux) *BlockProcessor {
+func NewBlockProcessor(db ethdb.Database, pow pow.PoW, blockchain *BlockChain, eventMux *event.TypeMux) *BlockProcessor {
sm := &BlockProcessor{
chainDb: db,
mem: make(map[string]*big.Int),
Pow: pow,
- bc: chainManager,
+ bc: blockchain,
eventMux: eventMux,
}
return sm
}
func (sm *BlockProcessor) TransitionState(statedb *state.StateDB, parent, block *types.Block, transientProcess bool) (receipts types.Receipts, err error) {
- gp := statedb.GetOrNewStateObject(block.Coinbase())
- gp.SetGasLimit(block.GasLimit())
+ gp := new(GasPool).AddGas(block.GasLimit())
+ if glog.V(logger.Core) {
+ glog.Infof("%x: gas (+ %v)", block.Coinbase(), gp)
+ }
// Process the transactions on to parent state
receipts, err = sm.ApplyTransactions(gp, statedb, block, block.Transactions(), transientProcess)
@@ -93,17 +111,15 @@ func (sm *BlockProcessor) TransitionState(statedb *state.StateDB, parent, block
return receipts, nil
}
-func (self *BlockProcessor) ApplyTransaction(gp GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, transientProcess bool) (*types.Receipt, *big.Int, error) {
+func (self *BlockProcessor) ApplyTransaction(gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, transientProcess bool) (*types.Receipt, *big.Int, error) {
_, gas, err := ApplyMessage(NewEnv(statedb, self.bc, tx, header), tx, gp)
if err != nil {
return nil, nil, err
}
// Update the state with pending changes
- statedb.SyncIntermediate()
-
usedGas.Add(usedGas, gas)
- receipt := types.NewReceipt(statedb.Root().Bytes(), usedGas)
+ receipt := types.NewReceipt(statedb.IntermediateRoot().Bytes(), usedGas)
receipt.TxHash = tx.Hash()
receipt.GasUsed = new(big.Int).Set(gas)
if MessageCreatesContract(tx) {
@@ -112,7 +128,7 @@ func (self *BlockProcessor) ApplyTransaction(gp GasPool, statedb *state.StateDB,
}
logs := statedb.GetLogs(tx.Hash())
- receipt.SetLogs(logs)
+ receipt.Logs = logs
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
glog.V(logger.Debug).Infoln(receipt)
@@ -125,11 +141,11 @@ func (self *BlockProcessor) ApplyTransaction(gp GasPool, statedb *state.StateDB,
return receipt, gas, err
}
-func (self *BlockProcessor) ChainManager() *ChainManager {
+func (self *BlockProcessor) BlockChain() *BlockChain {
return self.bc
}
-func (self *BlockProcessor) ApplyTransactions(gp GasPool, statedb *state.StateDB, block *types.Block, txs types.Transactions, transientProcess bool) (types.Receipts, error) {
+func (self *BlockProcessor) ApplyTransactions(gp *GasPool, statedb *state.StateDB, block *types.Block, txs types.Transactions, transientProcess bool) (types.Receipts, error) {
var (
receipts types.Receipts
totalUsedGas = big.NewInt(0)
@@ -165,7 +181,7 @@ func (self *BlockProcessor) ApplyTransactions(gp GasPool, statedb *state.StateDB
return receipts, err
}
-func (sm *BlockProcessor) RetryProcess(block *types.Block) (logs state.Logs, err error) {
+func (sm *BlockProcessor) RetryProcess(block *types.Block) (logs vm.Logs, err error) {
// Processing a blocks may never happen simultaneously
sm.mutex.Lock()
defer sm.mutex.Unlock()
@@ -190,25 +206,30 @@ func (sm *BlockProcessor) RetryProcess(block *types.Block) (logs state.Logs, err
// Process block will attempt to process the given block's transactions and applies them
// on top of the block's parent state (given it exists) and will return wether it was
// successful or not.
-func (sm *BlockProcessor) Process(block *types.Block) (logs state.Logs, receipts types.Receipts, err error) {
+func (sm *BlockProcessor) Process(block *types.Block) (logs vm.Logs, receipts types.Receipts, err error) {
// Processing a blocks may never happen simultaneously
sm.mutex.Lock()
defer sm.mutex.Unlock()
if sm.bc.HasBlock(block.Hash()) {
- return nil, nil, &KnownBlockError{block.Number(), block.Hash()}
+ if _, err := state.New(block.Root(), sm.chainDb); err == nil {
+ return nil, nil, &KnownBlockError{block.Number(), block.Hash()}
+ }
}
-
- if !sm.bc.HasBlock(block.ParentHash()) {
- return nil, nil, ParentError(block.ParentHash())
+ if parent := sm.bc.GetBlock(block.ParentHash()); parent != nil {
+ if _, err := state.New(parent.Root(), sm.chainDb); err == nil {
+ return sm.processWithParent(block, parent)
+ }
}
- parent := sm.bc.GetBlock(block.ParentHash())
- return sm.processWithParent(block, parent)
+ return nil, nil, ParentError(block.ParentHash())
}
-func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs state.Logs, receipts types.Receipts, err error) {
+func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs vm.Logs, receipts types.Receipts, err error) {
// Create a new state based on the parent's root (e.g., create copy)
- state := state.New(parent.Root(), sm.chainDb)
+ state, err := state.New(parent.Root(), sm.chainDb)
+ if err != nil {
+ return nil, nil, err
+ }
header := block.Header()
uncles := block.Uncles()
txs := block.Transactions()
@@ -265,16 +286,16 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs st
// Accumulate static rewards; block reward, uncle's and uncle inclusion.
AccumulateRewards(state, header, uncles)
- // Commit state objects/accounts to a temporary trie (does not save)
- // used to calculate the state root.
- state.SyncObjects()
- if header.Root != state.Root() {
- err = fmt.Errorf("invalid merkle root. received=%x got=%x", header.Root, state.Root())
- return
+ // Commit state objects/accounts to a database batch and calculate
+ // the state root. The database is not modified if the root
+ // doesn't match.
+ root, batch := state.CommitBatch()
+ if header.Root != root {
+ return nil, nil, fmt.Errorf("invalid merkle root: header=%x computed=%x", header.Root, root)
}
- // Sync the current block's state to the database
- state.Sync()
+ // Execute the database writes.
+ batch.Write()
return state.Logs(), receipts, nil
}
@@ -348,7 +369,7 @@ func (sm *BlockProcessor) VerifyUncles(statedb *state.StateDB, block, parent *ty
// GetBlockReceipts returns the receipts beloniging to the block hash
func (sm *BlockProcessor) GetBlockReceipts(bhash common.Hash) types.Receipts {
- if block := sm.ChainManager().GetBlock(bhash); block != nil {
+ if block := sm.BlockChain().GetBlock(bhash); block != nil {
return GetBlockReceipts(sm.chainDb, block.Hash())
}
@@ -358,22 +379,44 @@ func (sm *BlockProcessor) GetBlockReceipts(bhash common.Hash) types.Receipts {
// GetLogs returns the logs of the given block. This method is using a two step approach
// where it tries to get it from the (updated) method which gets them from the receipts or
// the depricated way by re-processing the block.
-func (sm *BlockProcessor) GetLogs(block *types.Block) (logs state.Logs, err error) {
+func (sm *BlockProcessor) GetLogs(block *types.Block) (logs vm.Logs, err error) {
receipts := GetBlockReceipts(sm.chainDb, block.Hash())
// coalesce logs
for _, receipt := range receipts {
- logs = append(logs, receipt.Logs()...)
+ logs = append(logs, receipt.Logs...)
}
return logs, nil
}
+// ValidateHeader verifies the validity of a header, relying on the database and
+// POW behind the block processor.
+func (sm *BlockProcessor) ValidateHeader(header *types.Header, checkPow, uncle bool) error {
+ // Short circuit if the header's already known or its parent missing
+ if sm.bc.HasHeader(header.Hash()) {
+ return nil
+ }
+ if parent := sm.bc.GetHeader(header.ParentHash); parent == nil {
+ return ParentError(header.ParentHash)
+ } else {
+ return ValidateHeader(sm.Pow, header, parent, checkPow, uncle)
+ }
+}
+
+// ValidateHeaderWithParent verifies the validity of a header, relying on the database and
+// POW behind the block processor.
+func (sm *BlockProcessor) ValidateHeaderWithParent(header, parent *types.Header, checkPow, uncle bool) error {
+ if sm.bc.HasHeader(header.Hash()) {
+ return nil
+ }
+ return ValidateHeader(sm.Pow, header, parent, checkPow, uncle)
+}
+
// See YP section 4.3.4. "Block Header Validity"
// Validates a header. Returns an error if the header is invalid.
func ValidateHeader(pow pow.PoW, header *types.Header, parent *types.Header, checkPow, uncle bool) error {
if big.NewInt(int64(len(header.Extra))).Cmp(params.MaximumExtraDataSize) == 1 {
return fmt.Errorf("Header extra data too long (%d)", len(header.Extra))
}
-
if uncle {
if header.Time.Cmp(common.MaxBig) == 1 {
return BlockTSTooBigErr
@@ -410,7 +453,7 @@ func ValidateHeader(pow pow.PoW, header *types.Header, parent *types.Header, che
if checkPow {
// Verify the nonce of the header. Return an error if it's not valid
if !pow.Verify(types.NewBlockWithHeader(header)) {
- return ValidationError("Header's nonce is invalid (= %x)", header.Nonce)
+ return &BlockNonceErr{Hash: header.Hash(), Number: header.Number, Nonce: header.Nonce.Uint64()}
}
}
return nil
diff --git a/core/block_processor_test.go b/core/block_processor_test.go
index 538cf4ee5..3050456b4 100644
--- a/core/block_processor_test.go
+++ b/core/block_processor_test.go
@@ -24,28 +24,29 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/pow/ezp"
)
-func proc() (*BlockProcessor, *ChainManager) {
+func proc() (*BlockProcessor, *BlockChain) {
db, _ := ethdb.NewMemDatabase()
var mux event.TypeMux
WriteTestNetGenesisBlock(db, 0)
- chainMan, err := NewChainManager(db, thePow(), &mux)
+ blockchain, err := NewBlockChain(db, thePow(), &mux)
if err != nil {
fmt.Println(err)
}
- return NewBlockProcessor(db, ezp.New(), chainMan, &mux), chainMan
+ return NewBlockProcessor(db, ezp.New(), blockchain, &mux), blockchain
}
func TestNumber(t *testing.T) {
pow := ezp.New()
_, chain := proc()
- statedb := state.New(chain.Genesis().Root(), chain.chainDb)
+ statedb, _ := state.New(chain.Genesis().Root(), chain.chainDb)
header := makeHeader(chain.Genesis(), statedb)
header.Number = big.NewInt(3)
err := ValidateHeader(pow, header, chain.Genesis().Header(), false, false)
@@ -69,16 +70,16 @@ func TestPutReceipt(t *testing.T) {
hash[0] = 2
receipt := new(types.Receipt)
- receipt.SetLogs(state.Logs{&state.Log{
- Address: addr,
- Topics: []common.Hash{hash},
- Data: []byte("hi"),
- Number: 42,
- TxHash: hash,
- TxIndex: 0,
- BlockHash: hash,
- Index: 0,
- }})
+ receipt.Logs = vm.Logs{&vm.Log{
+ Address: addr,
+ Topics: []common.Hash{hash},
+ Data: []byte("hi"),
+ BlockNumber: 42,
+ TxHash: hash,
+ TxIndex: 0,
+ BlockHash: hash,
+ Index: 0,
+ }}
PutReceipts(db, types.Receipts{receipt})
receipt = GetReceipt(db, common.Hash{})
diff --git a/core/blockchain.go b/core/blockchain.go
new file mode 100644
index 000000000..cea346e38
--- /dev/null
+++ b/core/blockchain.go
@@ -0,0 +1,1273 @@
+// Copyright 2014 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 implements the Ethereum consensus protocol.
+package core
+
+import (
+ crand "crypto/rand"
+ "errors"
+ "fmt"
+ "io"
+ "math"
+ "math/big"
+ mrand "math/rand"
+ "runtime"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/logger/glog"
+ "github.com/ethereum/go-ethereum/metrics"
+ "github.com/ethereum/go-ethereum/pow"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/trie"
+ "github.com/hashicorp/golang-lru"
+)
+
+var (
+ chainlogger = logger.NewLogger("CHAIN")
+ jsonlogger = logger.NewJsonLogger()
+
+ blockInsertTimer = metrics.NewTimer("chain/inserts")
+
+ ErrNoGenesis = errors.New("Genesis not found in chain")
+)
+
+const (
+ headerCacheLimit = 512
+ bodyCacheLimit = 256
+ tdCacheLimit = 1024
+ blockCacheLimit = 256
+ maxFutureBlocks = 256
+ maxTimeFutureBlocks = 30
+)
+
+type BlockChain struct {
+ chainDb ethdb.Database
+ processor types.BlockProcessor
+ eventMux *event.TypeMux
+ genesisBlock *types.Block
+ // Last known total difficulty
+ mu sync.RWMutex
+ chainmu sync.RWMutex
+ tsmu sync.RWMutex
+
+ checkpoint int // checkpoint counts towards the new checkpoint
+ currentHeader *types.Header // Current head of the header chain (may be above the block chain!)
+ currentBlock *types.Block // Current head of the block chain
+ currentFastBlock *types.Block // Current head of the fast-sync chain (may be above the block chain!)
+
+ headerCache *lru.Cache // Cache for the most recent block headers
+ bodyCache *lru.Cache // Cache for the most recent block bodies
+ bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
+ tdCache *lru.Cache // Cache for the most recent block total difficulties
+ blockCache *lru.Cache // Cache for the most recent entire blocks
+ futureBlocks *lru.Cache // future blocks are blocks added for later processing
+
+ quit chan struct{}
+ running int32 // running must be called automically
+ // procInterrupt must be atomically called
+ procInterrupt int32 // interrupt signaler for block processing
+ wg sync.WaitGroup
+
+ pow pow.PoW
+ rand *mrand.Rand
+}
+
+func NewBlockChain(chainDb ethdb.Database, pow pow.PoW, mux *event.TypeMux) (*BlockChain, error) {
+ headerCache, _ := lru.New(headerCacheLimit)
+ bodyCache, _ := lru.New(bodyCacheLimit)
+ bodyRLPCache, _ := lru.New(bodyCacheLimit)
+ tdCache, _ := lru.New(tdCacheLimit)
+ blockCache, _ := lru.New(blockCacheLimit)
+ futureBlocks, _ := lru.New(maxFutureBlocks)
+
+ bc := &BlockChain{
+ chainDb: chainDb,
+ eventMux: mux,
+ quit: make(chan struct{}),
+ headerCache: headerCache,
+ bodyCache: bodyCache,
+ bodyRLPCache: bodyRLPCache,
+ tdCache: tdCache,
+ blockCache: blockCache,
+ futureBlocks: futureBlocks,
+ pow: pow,
+ }
+ // Seed a fast but crypto originating random generator
+ seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
+ if err != nil {
+ return nil, err
+ }
+ bc.rand = mrand.New(mrand.NewSource(seed.Int64()))
+
+ bc.genesisBlock = bc.GetBlockByNumber(0)
+ if bc.genesisBlock == nil {
+ reader, err := NewDefaultGenesisReader()
+ if err != nil {
+ return nil, err
+ }
+ bc.genesisBlock, err = WriteGenesisBlock(chainDb, reader)
+ if err != nil {
+ return nil, err
+ }
+ glog.V(logger.Info).Infoln("WARNING: Wrote default ethereum genesis block")
+ }
+ if err := bc.loadLastState(); err != nil {
+ return nil, err
+ }
+ // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
+ for hash, _ := range BadHashes {
+ if header := bc.GetHeader(hash); header != nil {
+ glog.V(logger.Error).Infof("Found bad hash, rewinding chain to block #%d [%x…]", header.Number, header.ParentHash[:4])
+ bc.SetHead(header.Number.Uint64() - 1)
+ glog.V(logger.Error).Infoln("Chain rewind was successful, resuming normal operation")
+ }
+ }
+ // Take ownership of this particular state
+ go bc.update()
+ return bc, nil
+}
+
+// loadLastState loads the last known chain state from the database. This method
+// assumes that the chain manager mutex is held.
+func (self *BlockChain) loadLastState() error {
+ // Restore the last known head block
+ head := GetHeadBlockHash(self.chainDb)
+ if head == (common.Hash{}) {
+ // Corrupt or empty database, init from scratch
+ self.Reset()
+ } else {
+ if block := self.GetBlock(head); block != nil {
+ // Block found, set as the current head
+ self.currentBlock = block
+ } else {
+ // Corrupt or empty database, init from scratch
+ self.Reset()
+ }
+ }
+ // Restore the last known head header
+ self.currentHeader = self.currentBlock.Header()
+ if head := GetHeadHeaderHash(self.chainDb); head != (common.Hash{}) {
+ if header := self.GetHeader(head); header != nil {
+ self.currentHeader = header
+ }
+ }
+ // Restore the last known head fast block
+ self.currentFastBlock = self.currentBlock
+ if head := GetHeadFastBlockHash(self.chainDb); head != (common.Hash{}) {
+ if block := self.GetBlock(head); block != nil {
+ self.currentFastBlock = block
+ }
+ }
+ // Issue a status log and return
+ headerTd := self.GetTd(self.currentHeader.Hash())
+ blockTd := self.GetTd(self.currentBlock.Hash())
+ fastTd := self.GetTd(self.currentFastBlock.Hash())
+
+ glog.V(logger.Info).Infof("Last header: #%d [%x…] TD=%v", self.currentHeader.Number, self.currentHeader.Hash().Bytes()[:4], headerTd)
+ glog.V(logger.Info).Infof("Last block: #%d [%x…] TD=%v", self.currentBlock.Number(), self.currentBlock.Hash().Bytes()[:4], blockTd)
+ glog.V(logger.Info).Infof("Fast block: #%d [%x…] TD=%v", self.currentFastBlock.Number(), self.currentFastBlock.Hash().Bytes()[:4], fastTd)
+
+ return nil
+}
+
+// SetHead rewinds the local chain to a new head. In the case of headers, everything
+// above the new head will be deleted and the new one set. In the case of blocks
+// though, the head may be further rewound if block bodies are missing (non-archive
+// nodes after a fast sync).
+func (bc *BlockChain) SetHead(head uint64) {
+ bc.mu.Lock()
+ defer bc.mu.Unlock()
+
+ // Figure out the highest known canonical headers and/or blocks
+ height := uint64(0)
+ if bc.currentHeader != nil {
+ if hh := bc.currentHeader.Number.Uint64(); hh > height {
+ height = hh
+ }
+ }
+ if bc.currentBlock != nil {
+ if bh := bc.currentBlock.NumberU64(); bh > height {
+ height = bh
+ }
+ }
+ if bc.currentFastBlock != nil {
+ if fbh := bc.currentFastBlock.NumberU64(); fbh > height {
+ height = fbh
+ }
+ }
+ // Gather all the hashes that need deletion
+ drop := make(map[common.Hash]struct{})
+
+ for bc.currentHeader != nil && bc.currentHeader.Number.Uint64() > head {
+ drop[bc.currentHeader.Hash()] = struct{}{}
+ bc.currentHeader = bc.GetHeader(bc.currentHeader.ParentHash)
+ }
+ for bc.currentBlock != nil && bc.currentBlock.NumberU64() > head {
+ drop[bc.currentBlock.Hash()] = struct{}{}
+ bc.currentBlock = bc.GetBlock(bc.currentBlock.ParentHash())
+ }
+ for bc.currentFastBlock != nil && bc.currentFastBlock.NumberU64() > head {
+ drop[bc.currentFastBlock.Hash()] = struct{}{}
+ bc.currentFastBlock = bc.GetBlock(bc.currentFastBlock.ParentHash())
+ }
+ // Roll back the canonical chain numbering
+ for i := height; i > head; i-- {
+ DeleteCanonicalHash(bc.chainDb, i)
+ }
+ // Delete everything found by the above rewind
+ for hash, _ := range drop {
+ DeleteHeader(bc.chainDb, hash)
+ DeleteBody(bc.chainDb, hash)
+ DeleteTd(bc.chainDb, hash)
+ }
+ // Clear out any stale content from the caches
+ bc.headerCache.Purge()
+ bc.bodyCache.Purge()
+ bc.bodyRLPCache.Purge()
+ bc.blockCache.Purge()
+ bc.futureBlocks.Purge()
+
+ // Update all computed fields to the new head
+ if bc.currentBlock == nil {
+ bc.currentBlock = bc.genesisBlock
+ }
+ if bc.currentHeader == nil {
+ bc.currentHeader = bc.genesisBlock.Header()
+ }
+ if bc.currentFastBlock == nil {
+ bc.currentFastBlock = bc.genesisBlock
+ }
+ if err := WriteHeadBlockHash(bc.chainDb, bc.currentBlock.Hash()); err != nil {
+ glog.Fatalf("failed to reset head block hash: %v", err)
+ }
+ if err := WriteHeadHeaderHash(bc.chainDb, bc.currentHeader.Hash()); err != nil {
+ glog.Fatalf("failed to reset head header hash: %v", err)
+ }
+ if err := WriteHeadFastBlockHash(bc.chainDb, bc.currentFastBlock.Hash()); err != nil {
+ glog.Fatalf("failed to reset head fast block hash: %v", err)
+ }
+ bc.loadLastState()
+}
+
+// FastSyncCommitHead sets the current head block to the one defined by the hash
+// irrelevant what the chain contents were prior.
+func (self *BlockChain) FastSyncCommitHead(hash common.Hash) error {
+ // Make sure that both the block as well at its state trie exists
+ block := self.GetBlock(hash)
+ if block == nil {
+ return fmt.Errorf("non existent block [%x…]", hash[:4])
+ }
+ if _, err := trie.NewSecure(block.Root(), self.chainDb); err != nil {
+ return err
+ }
+ // If all checks out, manually set the head block
+ self.mu.Lock()
+ self.currentBlock = block
+ self.mu.Unlock()
+
+ glog.V(logger.Info).Infof("committed block #%d [%x…] as new head", block.Number(), hash[:4])
+ return nil
+}
+
+func (self *BlockChain) GasLimit() *big.Int {
+ self.mu.RLock()
+ defer self.mu.RUnlock()
+
+ return self.currentBlock.GasLimit()
+}
+
+func (self *BlockChain) LastBlockHash() common.Hash {
+ self.mu.RLock()
+ defer self.mu.RUnlock()
+
+ return self.currentBlock.Hash()
+}
+
+// CurrentHeader retrieves the current head header of the canonical chain. The
+// header is retrieved from the blockchain's internal cache.
+func (self *BlockChain) CurrentHeader() *types.Header {
+ self.mu.RLock()
+ defer self.mu.RUnlock()
+
+ return self.currentHeader
+}
+
+// CurrentBlock retrieves the current head block of the canonical chain. The
+// block is retrieved from the blockchain's internal cache.
+func (self *BlockChain) CurrentBlock() *types.Block {
+ self.mu.RLock()
+ defer self.mu.RUnlock()
+
+ return self.currentBlock
+}
+
+// CurrentFastBlock retrieves the current fast-sync head block of the canonical
+// chain. The block is retrieved from the blockchain's internal cache.
+func (self *BlockChain) CurrentFastBlock() *types.Block {
+ self.mu.RLock()
+ defer self.mu.RUnlock()
+
+ return self.currentFastBlock
+}
+
+func (self *BlockChain) Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash) {
+ self.mu.RLock()
+ defer self.mu.RUnlock()
+
+ return self.GetTd(self.currentBlock.Hash()), self.currentBlock.Hash(), self.genesisBlock.Hash()
+}
+
+func (self *BlockChain) SetProcessor(proc types.BlockProcessor) {
+ self.processor = proc
+}
+
+func (self *BlockChain) State() (*state.StateDB, error) {
+ return state.New(self.CurrentBlock().Root(), self.chainDb)
+}
+
+// Reset purges the entire blockchain, restoring it to its genesis state.
+func (bc *BlockChain) Reset() {
+ bc.ResetWithGenesisBlock(bc.genesisBlock)
+}
+
+// ResetWithGenesisBlock purges the entire blockchain, restoring it to the
+// specified genesis state.
+func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) {
+ // Dump the entire block chain and purge the caches
+ bc.SetHead(0)
+
+ bc.mu.Lock()
+ defer bc.mu.Unlock()
+
+ // Prepare the genesis block and reinitialise the chain
+ if err := WriteTd(bc.chainDb, genesis.Hash(), genesis.Difficulty()); err != nil {
+ glog.Fatalf("failed to write genesis block TD: %v", err)
+ }
+ if err := WriteBlock(bc.chainDb, genesis); err != nil {
+ glog.Fatalf("failed to write genesis block: %v", err)
+ }
+ bc.genesisBlock = genesis
+ bc.insert(bc.genesisBlock)
+ bc.currentBlock = bc.genesisBlock
+ bc.currentHeader = bc.genesisBlock.Header()
+ bc.currentFastBlock = bc.genesisBlock
+}
+
+// Export writes the active chain to the given writer.
+func (self *BlockChain) Export(w io.Writer) error {
+ if err := self.ExportN(w, uint64(0), self.currentBlock.NumberU64()); err != nil {
+ return err
+ }
+ return nil
+}
+
+// ExportN writes a subset of the active chain to the given writer.
+func (self *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {
+ self.mu.RLock()
+ defer self.mu.RUnlock()
+
+ if first > last {
+ return fmt.Errorf("export failed: first (%d) is greater than last (%d)", first, last)
+ }
+
+ glog.V(logger.Info).Infof("exporting %d blocks...\n", last-first+1)
+
+ for nr := first; nr <= last; nr++ {
+ block := self.GetBlockByNumber(nr)
+ if block == nil {
+ return fmt.Errorf("export failed on #%d: not found", nr)
+ }
+
+ if err := block.EncodeRLP(w); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// insert injects a new head block into the current block chain. This method
+// assumes that the block is indeed a true head. It will also reset the head
+// header and the head fast sync block to this very same block if they are older
+// or if they are on a different side chain.
+//
+// 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.chainDb, block.NumberU64()) != block.Hash()
+
+ // Add the block to the canonical chain number scheme and mark as the head
+ if err := WriteCanonicalHash(bc.chainDb, block.Hash(), block.NumberU64()); err != nil {
+ glog.Fatalf("failed to insert block number: %v", err)
+ }
+ if err := WriteHeadBlockHash(bc.chainDb, block.Hash()); err != nil {
+ glog.Fatalf("failed to insert head block hash: %v", err)
+ }
+ bc.currentBlock = block
+
+ // If the block is better than out head or is on a different chain, force update heads
+ if updateHeads {
+ if err := WriteHeadHeaderHash(bc.chainDb, block.Hash()); err != nil {
+ glog.Fatalf("failed to insert head header hash: %v", err)
+ }
+ bc.currentHeader = block.Header()
+
+ if err := WriteHeadFastBlockHash(bc.chainDb, block.Hash()); err != nil {
+ glog.Fatalf("failed to insert head fast block hash: %v", err)
+ }
+ bc.currentFastBlock = block
+ }
+}
+
+// Accessors
+func (bc *BlockChain) Genesis() *types.Block {
+ return bc.genesisBlock
+}
+
+// HasHeader checks if a block header is present in the database or not, caching
+// it if present.
+func (bc *BlockChain) HasHeader(hash common.Hash) bool {
+ return bc.GetHeader(hash) != nil
+}
+
+// GetHeader retrieves a block header from the database by hash, caching it if
+// found.
+func (self *BlockChain) GetHeader(hash common.Hash) *types.Header {
+ // Short circuit if the header's already in the cache, retrieve otherwise
+ if header, ok := self.headerCache.Get(hash); ok {
+ return header.(*types.Header)
+ }
+ header := GetHeader(self.chainDb, hash)
+ if header == nil {
+ return nil
+ }
+ // Cache the found header for next time and return
+ self.headerCache.Add(header.Hash(), header)
+ return header
+}
+
+// GetHeaderByNumber retrieves a block header from the database by number,
+// caching it (associated with its hash) if found.
+func (self *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
+ hash := GetCanonicalHash(self.chainDb, number)
+ if hash == (common.Hash{}) {
+ return nil
+ }
+ return self.GetHeader(hash)
+}
+
+// GetBody retrieves a block body (transactions and uncles) from the database by
+// hash, caching it if found.
+func (self *BlockChain) GetBody(hash common.Hash) *types.Body {
+ // Short circuit if the body's already in the cache, retrieve otherwise
+ if cached, ok := self.bodyCache.Get(hash); ok {
+ body := cached.(*types.Body)
+ return body
+ }
+ body := GetBody(self.chainDb, hash)
+ if body == nil {
+ return nil
+ }
+ // Cache the found body for next time and return
+ self.bodyCache.Add(hash, body)
+ return body
+}
+
+// GetBodyRLP retrieves a block body in RLP encoding from the database by hash,
+// caching it if found.
+func (self *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue {
+ // Short circuit if the body's already in the cache, retrieve otherwise
+ if cached, ok := self.bodyRLPCache.Get(hash); ok {
+ return cached.(rlp.RawValue)
+ }
+ body := GetBodyRLP(self.chainDb, hash)
+ if len(body) == 0 {
+ return nil
+ }
+ // Cache the found body for next time and return
+ self.bodyRLPCache.Add(hash, body)
+ return body
+}
+
+// GetTd retrieves a block's total difficulty in the canonical chain from the
+// database by hash, caching it if found.
+func (self *BlockChain) GetTd(hash common.Hash) *big.Int {
+ // Short circuit if the td's already in the cache, retrieve otherwise
+ if cached, ok := self.tdCache.Get(hash); ok {
+ return cached.(*big.Int)
+ }
+ td := GetTd(self.chainDb, hash)
+ if td == nil {
+ return nil
+ }
+ // Cache the found body for next time and return
+ self.tdCache.Add(hash, td)
+ return td
+}
+
+// HasBlock checks if a block is fully present in the database or not, caching
+// it if present.
+func (bc *BlockChain) HasBlock(hash common.Hash) bool {
+ return bc.GetBlock(hash) != nil
+}
+
+// GetBlock retrieves a block from the database by hash, caching it if found.
+func (self *BlockChain) GetBlock(hash common.Hash) *types.Block {
+ // Short circuit if the block's already in the cache, retrieve otherwise
+ if block, ok := self.blockCache.Get(hash); ok {
+ return block.(*types.Block)
+ }
+ block := GetBlock(self.chainDb, hash)
+ if block == nil {
+ return nil
+ }
+ // Cache the found block for next time and return
+ self.blockCache.Add(block.Hash(), block)
+ return block
+}
+
+// GetBlockByNumber retrieves a block from the database by number, caching it
+// (associated with its hash) if found.
+func (self *BlockChain) GetBlockByNumber(number uint64) *types.Block {
+ hash := GetCanonicalHash(self.chainDb, number)
+ if hash == (common.Hash{}) {
+ return nil
+ }
+ return self.GetBlock(hash)
+}
+
+// GetBlockHashesFromHash retrieves a number of block hashes starting at a given
+// hash, fetching towards the genesis block.
+func (self *BlockChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []common.Hash {
+ // Get the origin header from which to fetch
+ header := self.GetHeader(hash)
+ if header == nil {
+ return nil
+ }
+ // Iterate the headers until enough is collected or the genesis reached
+ chain := make([]common.Hash, 0, max)
+ for i := uint64(0); i < max; i++ {
+ if header = self.GetHeader(header.ParentHash); header == nil {
+ break
+ }
+ chain = append(chain, header.Hash())
+ if header.Number.Cmp(common.Big0) == 0 {
+ break
+ }
+ }
+ return chain
+}
+
+// [deprecated by eth/62]
+// GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors.
+func (self *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*types.Block) {
+ for i := 0; i < n; i++ {
+ block := self.GetBlock(hash)
+ if block == nil {
+ break
+ }
+ blocks = append(blocks, block)
+ hash = block.ParentHash()
+ }
+ return
+}
+
+// GetUnclesInChain retrieves all the uncles from a given block backwards until
+// a specific distance is reached.
+func (self *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.Header {
+ uncles := []*types.Header{}
+ for i := 0; block != nil && i < length; i++ {
+ uncles = append(uncles, block.Uncles()...)
+ block = self.GetBlock(block.ParentHash())
+ }
+ return uncles
+}
+
+func (bc *BlockChain) Stop() {
+ if !atomic.CompareAndSwapInt32(&bc.running, 0, 1) {
+ return
+ }
+ close(bc.quit)
+ atomic.StoreInt32(&bc.procInterrupt, 1)
+
+ bc.wg.Wait()
+
+ glog.V(logger.Info).Infoln("Chain manager stopped")
+}
+
+func (self *BlockChain) procFutureBlocks() {
+ blocks := make([]*types.Block, self.futureBlocks.Len())
+ for i, hash := range self.futureBlocks.Keys() {
+ block, _ := self.futureBlocks.Get(hash)
+ blocks[i] = block.(*types.Block)
+ }
+ if len(blocks) > 0 {
+ types.BlockBy(types.Number).Sort(blocks)
+ self.InsertChain(blocks)
+ }
+}
+
+type writeStatus byte
+
+const (
+ NonStatTy writeStatus = iota
+ CanonStatTy
+ SplitStatTy
+ SideStatTy
+)
+
+// writeHeader writes a header into the local chain, given that its parent is
+// already known. If the total difficulty of the newly inserted header becomes
+// greater than the current known TD, the canonical chain is re-routed.
+//
+// Note: This method is not concurrent-safe with inserting blocks simultaneously
+// into the chain, as side effects caused by reorganisations cannot be emulated
+// without the real blocks. Hence, writing headers directly should only be done
+// in two scenarios: pure-header mode of operation (light clients), or properly
+// separated header/block phases (non-archive clients).
+func (self *BlockChain) writeHeader(header *types.Header) error {
+ self.wg.Add(1)
+ defer self.wg.Done()
+
+ // Calculate the total difficulty of the header
+ ptd := self.GetTd(header.ParentHash)
+ if ptd == nil {
+ return ParentError(header.ParentHash)
+ }
+ td := new(big.Int).Add(header.Difficulty, ptd)
+
+ // Make sure no inconsistent state is leaked during insertion
+ self.mu.Lock()
+ defer self.mu.Unlock()
+
+ // If the total difficulty is higher than our known, add it to the canonical chain
+ if td.Cmp(self.GetTd(self.currentHeader.Hash())) > 0 {
+ // Delete any canonical number assignments above the new head
+ for i := header.Number.Uint64() + 1; GetCanonicalHash(self.chainDb, i) != (common.Hash{}); i++ {
+ DeleteCanonicalHash(self.chainDb, i)
+ }
+ // Overwrite any stale canonical number assignments
+ head := self.GetHeader(header.ParentHash)
+ for GetCanonicalHash(self.chainDb, head.Number.Uint64()) != head.Hash() {
+ WriteCanonicalHash(self.chainDb, head.Hash(), head.Number.Uint64())
+ head = self.GetHeader(head.ParentHash)
+ }
+ // Extend the canonical chain with the new header
+ if err := WriteCanonicalHash(self.chainDb, header.Hash(), header.Number.Uint64()); err != nil {
+ glog.Fatalf("failed to insert header number: %v", err)
+ }
+ if err := WriteHeadHeaderHash(self.chainDb, header.Hash()); err != nil {
+ glog.Fatalf("failed to insert head header hash: %v", err)
+ }
+ self.currentHeader = types.CopyHeader(header)
+ }
+ // Irrelevant of the canonical status, write the header itself to the database
+ if err := WriteTd(self.chainDb, header.Hash(), td); err != nil {
+ glog.Fatalf("failed to write header total difficulty: %v", err)
+ }
+ if err := WriteHeader(self.chainDb, header); err != nil {
+ glog.Fatalf("filed to write header contents: %v", err)
+ }
+ return nil
+}
+
+// InsertHeaderChain attempts to insert the given header chain in to the local
+// chain, possibly creating a reorg. If an error is returned, it will return the
+// index number of the failing header as well an error describing what went wrong.
+//
+// The verify parameter can be used to fine tune whether nonce verification
+// should be done or not. The reason behind the optional check is because some
+// of the header retrieval mechanisms already need to verfy nonces, as well as
+// because nonces can be verified sparsely, not needing to check each.
+func (self *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (int, error) {
+ self.wg.Add(1)
+ defer self.wg.Done()
+
+ // Make sure only one thread manipulates the chain at once
+ self.chainmu.Lock()
+ defer self.chainmu.Unlock()
+
+ // Collect some import statistics to report on
+ stats := struct{ processed, ignored int }{}
+ start := time.Now()
+
+ // Generate the list of headers that should be POW verified
+ verify := make([]bool, len(chain))
+ for i := 0; i < len(verify)/checkFreq; i++ {
+ index := i*checkFreq + self.rand.Intn(checkFreq)
+ if index >= len(verify) {
+ index = len(verify) - 1
+ }
+ verify[index] = true
+ }
+ verify[len(verify)-1] = true // Last should always be verified to avoid junk
+
+ // Create the header verification task queue and worker functions
+ tasks := make(chan int, len(chain))
+ for i := 0; i < len(chain); i++ {
+ tasks <- i
+ }
+ close(tasks)
+
+ errs, failed := make([]error, len(tasks)), int32(0)
+ process := func(worker int) {
+ for index := range tasks {
+ header, hash := chain[index], chain[index].Hash()
+
+ // Short circuit insertion if shutting down or processing failed
+ if atomic.LoadInt32(&self.procInterrupt) == 1 {
+ return
+ }
+ if atomic.LoadInt32(&failed) > 0 {
+ return
+ }
+ // Short circuit if the header is bad or already known
+ if BadHashes[hash] {
+ errs[index] = BadHashError(hash)
+ atomic.AddInt32(&failed, 1)
+ return
+ }
+ if self.HasHeader(hash) {
+ continue
+ }
+ // Verify that the header honors the chain parameters
+ checkPow := verify[index]
+
+ var err error
+ if index == 0 {
+ err = self.processor.ValidateHeader(header, checkPow, false)
+ } else {
+ err = self.processor.ValidateHeaderWithParent(header, chain[index-1], checkPow, false)
+ }
+ if err != nil {
+ errs[index] = err
+ atomic.AddInt32(&failed, 1)
+ return
+ }
+ }
+ }
+ // Start as many worker threads as goroutines allowed
+ pending := new(sync.WaitGroup)
+ for i := 0; i < runtime.GOMAXPROCS(0); i++ {
+ pending.Add(1)
+ go func(id int) {
+ defer pending.Done()
+ process(id)
+ }(i)
+ }
+ pending.Wait()
+
+ // If anything failed, report
+ if failed > 0 {
+ for i, err := range errs {
+ if err != nil {
+ return i, err
+ }
+ }
+ }
+ // All headers passed verification, import them into the database
+ for i, header := range chain {
+ // Short circuit insertion if shutting down
+ if atomic.LoadInt32(&self.procInterrupt) == 1 {
+ glog.V(logger.Debug).Infoln("premature abort during header chain processing")
+ break
+ }
+ hash := header.Hash()
+
+ // If the header's already known, skip it, otherwise store
+ if self.HasHeader(hash) {
+ stats.ignored++
+ continue
+ }
+ if err := self.writeHeader(header); err != nil {
+ return i, err
+ }
+ stats.processed++
+ }
+ // Report some public statistics so the user has a clue what's going on
+ first, last := chain[0], chain[len(chain)-1]
+ glog.V(logger.Info).Infof("imported %d header(s) (%d ignored) in %v. #%v [%x… / %x…]", stats.processed, stats.ignored,
+ time.Since(start), last.Number, first.Hash().Bytes()[:4], last.Hash().Bytes()[:4])
+
+ return 0, nil
+}
+
+// Rollback is designed to remove a chain of links from the database that aren't
+// certain enough to be valid.
+func (self *BlockChain) Rollback(chain []common.Hash) {
+ self.mu.Lock()
+ defer self.mu.Unlock()
+
+ for i := len(chain) - 1; i >= 0; i-- {
+ hash := chain[i]
+
+ if self.currentHeader.Hash() == hash {
+ self.currentHeader = self.GetHeader(self.currentHeader.ParentHash)
+ WriteHeadHeaderHash(self.chainDb, self.currentHeader.Hash())
+ }
+ if self.currentFastBlock.Hash() == hash {
+ self.currentFastBlock = self.GetBlock(self.currentFastBlock.ParentHash())
+ WriteHeadFastBlockHash(self.chainDb, self.currentFastBlock.Hash())
+ }
+ if self.currentBlock.Hash() == hash {
+ self.currentBlock = self.GetBlock(self.currentBlock.ParentHash())
+ WriteHeadBlockHash(self.chainDb, self.currentBlock.Hash())
+ }
+ }
+}
+
+// InsertReceiptChain attempts to complete an already existing header chain with
+// transaction and receipt data.
+func (self *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {
+ self.wg.Add(1)
+ defer self.wg.Done()
+
+ // Collect some import statistics to report on
+ stats := struct{ processed, ignored int32 }{}
+ start := time.Now()
+
+ // Create the block importing task queue and worker functions
+ tasks := make(chan int, len(blockChain))
+ for i := 0; i < len(blockChain) && i < len(receiptChain); i++ {
+ tasks <- i
+ }
+ close(tasks)
+
+ errs, failed := make([]error, len(tasks)), int32(0)
+ process := func(worker int) {
+ for index := range tasks {
+ block, receipts := blockChain[index], receiptChain[index]
+
+ // Short circuit insertion if shutting down or processing failed
+ if atomic.LoadInt32(&self.procInterrupt) == 1 {
+ return
+ }
+ if atomic.LoadInt32(&failed) > 0 {
+ return
+ }
+ // Short circuit if the owner header is unknown
+ if !self.HasHeader(block.Hash()) {
+ errs[index] = fmt.Errorf("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4])
+ atomic.AddInt32(&failed, 1)
+ return
+ }
+ // Skip if the entire data is already known
+ if self.HasBlock(block.Hash()) {
+ atomic.AddInt32(&stats.ignored, 1)
+ continue
+ }
+ // Compute all the non-consensus fields of the receipts
+ transactions, logIndex := block.Transactions(), uint(0)
+ for j := 0; j < len(receipts); j++ {
+ // The transaction hash can be retrieved from the transaction itself
+ receipts[j].TxHash = transactions[j].Hash()
+
+ // The contract address can be derived from the transaction itself
+ if MessageCreatesContract(transactions[j]) {
+ from, _ := transactions[j].From()
+ receipts[j].ContractAddress = crypto.CreateAddress(from, transactions[j].Nonce())
+ }
+ // The used gas can be calculated based on previous receipts
+ if j == 0 {
+ receipts[j].GasUsed = new(big.Int).Set(receipts[j].CumulativeGasUsed)
+ } else {
+ receipts[j].GasUsed = new(big.Int).Sub(receipts[j].CumulativeGasUsed, receipts[j-1].CumulativeGasUsed)
+ }
+ // The derived log fields can simply be set from the block and transaction
+ for k := 0; k < len(receipts[j].Logs); k++ {
+ receipts[j].Logs[k].BlockNumber = block.NumberU64()
+ receipts[j].Logs[k].BlockHash = block.Hash()
+ receipts[j].Logs[k].TxHash = receipts[j].TxHash
+ receipts[j].Logs[k].TxIndex = uint(j)
+ receipts[j].Logs[k].Index = logIndex
+ logIndex++
+ }
+ }
+ // Write all the data out into the database
+ if err := WriteBody(self.chainDb, block.Hash(), &types.Body{block.Transactions(), block.Uncles()}); err != nil {
+ errs[index] = fmt.Errorf("failed to write block body: %v", err)
+ atomic.AddInt32(&failed, 1)
+ glog.Fatal(errs[index])
+ return
+ }
+ if err := PutBlockReceipts(self.chainDb, block.Hash(), receipts); err != nil {
+ errs[index] = fmt.Errorf("failed to write block receipts: %v", err)
+ atomic.AddInt32(&failed, 1)
+ glog.Fatal(errs[index])
+ return
+ }
+ if err := WriteMipmapBloom(self.chainDb, block.NumberU64(), receipts); err != nil {
+ errs[index] = fmt.Errorf("failed to write log blooms: %v", err)
+ atomic.AddInt32(&failed, 1)
+ glog.Fatal(errs[index])
+ return
+ }
+ atomic.AddInt32(&stats.processed, 1)
+ }
+ }
+ // Start as many worker threads as goroutines allowed
+ pending := new(sync.WaitGroup)
+ for i := 0; i < runtime.GOMAXPROCS(0); i++ {
+ pending.Add(1)
+ go func(id int) {
+ defer pending.Done()
+ process(id)
+ }(i)
+ }
+ pending.Wait()
+
+ // If anything failed, report
+ if failed > 0 {
+ for i, err := range errs {
+ if err != nil {
+ return i, err
+ }
+ }
+ }
+ if atomic.LoadInt32(&self.procInterrupt) == 1 {
+ glog.V(logger.Debug).Infoln("premature abort during receipt chain processing")
+ return 0, nil
+ }
+ // Update the head fast sync block if better
+ self.mu.Lock()
+ head := blockChain[len(errs)-1]
+ if self.GetTd(self.currentFastBlock.Hash()).Cmp(self.GetTd(head.Hash())) < 0 {
+ if err := WriteHeadFastBlockHash(self.chainDb, head.Hash()); err != nil {
+ glog.Fatalf("failed to update head fast block hash: %v", err)
+ }
+ self.currentFastBlock = head
+ }
+ self.mu.Unlock()
+
+ // Report some public statistics so the user has a clue what's going on
+ first, last := blockChain[0], blockChain[len(blockChain)-1]
+ glog.V(logger.Info).Infof("imported %d receipt(s) (%d ignored) in %v. #%d [%x… / %x…]", stats.processed, stats.ignored,
+ time.Since(start), last.Number(), first.Hash().Bytes()[:4], last.Hash().Bytes()[:4])
+
+ return 0, nil
+}
+
+// WriteBlock writes the block to the chain.
+func (self *BlockChain) WriteBlock(block *types.Block) (status writeStatus, err error) {
+ self.wg.Add(1)
+ defer self.wg.Done()
+
+ // Calculate the total difficulty of the block
+ ptd := self.GetTd(block.ParentHash())
+ if ptd == nil {
+ return NonStatTy, ParentError(block.ParentHash())
+ }
+ td := new(big.Int).Add(block.Difficulty(), ptd)
+
+ // Make sure no inconsistent state is leaked during insertion
+ self.mu.Lock()
+ defer self.mu.Unlock()
+
+ // If the total difficulty is higher than our known, add it to the canonical chain
+ if td.Cmp(self.GetTd(self.currentBlock.Hash())) > 0 {
+ // Reorganize the chain if the parent is not the head block
+ if block.ParentHash() != self.currentBlock.Hash() {
+ if err := self.reorg(self.currentBlock, block); err != nil {
+ return NonStatTy, err
+ }
+ }
+ // Insert the block as the new head of the chain
+ self.insert(block)
+ status = CanonStatTy
+ } else {
+ status = SideStatTy
+ }
+ // Irrelevant of the canonical status, write the block itself to the database
+ if err := WriteTd(self.chainDb, block.Hash(), td); err != nil {
+ glog.Fatalf("failed to write block total difficulty: %v", err)
+ }
+ if err := WriteBlock(self.chainDb, block); err != nil {
+ glog.Fatalf("filed to write block contents: %v", err)
+ }
+ self.futureBlocks.Remove(block.Hash())
+
+ return
+}
+
+// InsertChain will attempt to insert the given chain in to the canonical chain or, otherwise, create a fork. It an error is returned
+// it will return the index number of the failing block as well an error describing what went wrong (for possible errors see core/errors.go).
+func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) {
+ self.wg.Add(1)
+ defer self.wg.Done()
+
+ self.chainmu.Lock()
+ defer self.chainmu.Unlock()
+
+ // A queued approach to delivering events. This is generally
+ // faster than direct delivery and requires much less mutex
+ // acquiring.
+ var (
+ stats struct{ queued, processed, ignored int }
+ events = make([]interface{}, 0, len(chain))
+ tstart = time.Now()
+
+ nonceChecked = make([]bool, len(chain))
+ )
+
+ // Start the parallel nonce verifier.
+ nonceAbort, nonceResults := verifyNoncesFromBlocks(self.pow, chain)
+ defer close(nonceAbort)
+
+ txcount := 0
+ for i, block := range chain {
+ if atomic.LoadInt32(&self.procInterrupt) == 1 {
+ glog.V(logger.Debug).Infoln("Premature abort during block chain processing")
+ break
+ }
+
+ bstart := time.Now()
+ // Wait for block i's nonce to be verified before processing
+ // its state transition.
+ for !nonceChecked[i] {
+ r := <-nonceResults
+ nonceChecked[r.index] = true
+ if !r.valid {
+ block := chain[r.index]
+ return r.index, &BlockNonceErr{Hash: block.Hash(), Number: block.Number(), Nonce: block.Nonce()}
+ }
+ }
+
+ if BadHashes[block.Hash()] {
+ err := BadHashError(block.Hash())
+ blockErr(block, err)
+ return i, err
+ }
+ // Call in to the block processor and check for errors. It's likely that if one block fails
+ // all others will fail too (unless a known block is returned).
+ logs, receipts, err := self.processor.Process(block)
+ if err != nil {
+ if IsKnownBlockErr(err) {
+ stats.ignored++
+ continue
+ }
+
+ if err == BlockFutureErr {
+ // Allow up to MaxFuture second in the future blocks. If this limit
+ // is exceeded the chain is discarded and processed at a later time
+ // if given.
+ max := big.NewInt(time.Now().Unix() + maxTimeFutureBlocks)
+ if block.Time().Cmp(max) == 1 {
+ return i, fmt.Errorf("%v: BlockFutureErr, %v > %v", BlockFutureErr, block.Time(), max)
+ }
+
+ self.futureBlocks.Add(block.Hash(), block)
+ stats.queued++
+ continue
+ }
+
+ if IsParentErr(err) && self.futureBlocks.Contains(block.ParentHash()) {
+ self.futureBlocks.Add(block.Hash(), block)
+ stats.queued++
+ continue
+ }
+
+ blockErr(block, err)
+
+ go ReportBlock(block, err)
+
+ return i, err
+ }
+ if err := PutBlockReceipts(self.chainDb, block.Hash(), receipts); err != nil {
+ glog.V(logger.Warn).Infoln("error writing block receipts:", err)
+ }
+
+ txcount += len(block.Transactions())
+ // write the block to the chain and get the status
+ status, err := self.WriteBlock(block)
+ if err != nil {
+ return i, err
+ }
+ switch status {
+ case CanonStatTy:
+ if glog.V(logger.Debug) {
+ glog.Infof("[%v] inserted block #%d (%d TXs %v G %d UNCs) (%x...). Took %v\n", time.Now().UnixNano(), block.Number(), len(block.Transactions()), block.GasUsed(), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart))
+ }
+ events = append(events, ChainEvent{block, block.Hash(), logs})
+
+ // This puts transactions in a extra db for rpc
+ if err := PutTransactions(self.chainDb, block, block.Transactions()); err != nil {
+ return i, err
+ }
+ // store the receipts
+ if err := PutReceipts(self.chainDb, receipts); err != nil {
+ return i, err
+ }
+ // Write map map bloom filters
+ if err := WriteMipmapBloom(self.chainDb, block.NumberU64(), receipts); err != nil {
+ return i, err
+ }
+ case SideStatTy:
+ if glog.V(logger.Detail) {
+ glog.Infof("inserted forked block #%d (TD=%v) (%d TXs %d UNCs) (%x...). Took %v\n", block.Number(), block.Difficulty(), len(block.Transactions()), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart))
+ }
+ events = append(events, ChainSideEvent{block, logs})
+
+ case SplitStatTy:
+ events = append(events, ChainSplitEvent{block, logs})
+ }
+ stats.processed++
+ }
+
+ if (stats.queued > 0 || stats.processed > 0 || stats.ignored > 0) && bool(glog.V(logger.Info)) {
+ tend := time.Since(tstart)
+ start, end := chain[0], chain[len(chain)-1]
+ glog.Infof("imported %d block(s) (%d queued %d ignored) including %d txs in %v. #%v [%x / %x]\n", stats.processed, stats.queued, stats.ignored, txcount, tend, end.Number(), start.Hash().Bytes()[:4], end.Hash().Bytes()[:4])
+ }
+ go self.postChainEvents(events)
+
+ return 0, nil
+}
+
+// reorgs takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them
+// to be part of the new canonical chain and accumulates potential missing transactions and post an
+// event about them
+func (self *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
+ var (
+ newChain types.Blocks
+ commonBlock *types.Block
+ oldStart = oldBlock
+ newStart = newBlock
+ deletedTxs types.Transactions
+ )
+
+ // first reduce whoever is higher bound
+ if oldBlock.NumberU64() > newBlock.NumberU64() {
+ // reduce old chain
+ for oldBlock = oldBlock; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = self.GetBlock(oldBlock.ParentHash()) {
+ deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
+ }
+ } else {
+ // reduce new chain and append new chain blocks for inserting later on
+ for newBlock = newBlock; newBlock != nil && newBlock.NumberU64() != oldBlock.NumberU64(); newBlock = self.GetBlock(newBlock.ParentHash()) {
+ newChain = append(newChain, newBlock)
+ }
+ }
+ if oldBlock == nil {
+ return fmt.Errorf("Invalid old chain")
+ }
+ if newBlock == nil {
+ return fmt.Errorf("Invalid new chain")
+ }
+
+ numSplit := newBlock.Number()
+ for {
+ if oldBlock.Hash() == newBlock.Hash() {
+ commonBlock = oldBlock
+ break
+ }
+ newChain = append(newChain, newBlock)
+ deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
+
+ oldBlock, newBlock = self.GetBlock(oldBlock.ParentHash()), self.GetBlock(newBlock.ParentHash())
+ if oldBlock == nil {
+ return fmt.Errorf("Invalid old chain")
+ }
+ if newBlock == nil {
+ return fmt.Errorf("Invalid new chain")
+ }
+ }
+
+ if glog.V(logger.Debug) {
+ commonHash := commonBlock.Hash()
+ glog.Infof("Chain split detected @ %x. Reorganising chain from #%v %x to %x", commonHash[:4], numSplit, oldStart.Hash().Bytes()[:4], newStart.Hash().Bytes()[:4])
+ }
+
+ var addedTxs types.Transactions
+ // insert blocks. Order does not matter. Last block will be written in ImportChain itself which creates the new head properly
+ for _, block := range newChain {
+ // insert the block in the canonical way, re-writing history
+ self.insert(block)
+ // write canonical receipts and transactions
+ if err := PutTransactions(self.chainDb, block, block.Transactions()); err != nil {
+ return err
+ }
+ receipts := GetBlockReceipts(self.chainDb, block.Hash())
+ // write receipts
+ if err := PutReceipts(self.chainDb, receipts); err != nil {
+ return err
+ }
+ // Write map map bloom filters
+ if err := WriteMipmapBloom(self.chainDb, block.NumberU64(), receipts); err != nil {
+ return err
+ }
+
+ addedTxs = append(addedTxs, block.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
+ for _, tx := range diff {
+ DeleteReceipt(self.chainDb, tx.Hash())
+ DeleteTransaction(self.chainDb, tx.Hash())
+ }
+ // Must be posted in a goroutine because of the transaction pool trying
+ // to acquire the chain manager lock
+ go self.eventMux.Post(RemovedTransactionEvent{diff})
+
+ return nil
+}
+
+// postChainEvents iterates over the events generated by a chain insertion and
+// posts them into the event mux.
+func (self *BlockChain) postChainEvents(events []interface{}) {
+ for _, event := range events {
+ if event, ok := event.(ChainEvent); ok {
+ // We need some control over the mining operation. Acquiring locks and waiting for the miner to create new block takes too long
+ // and in most cases isn't even necessary.
+ if self.LastBlockHash() == event.Hash {
+ self.eventMux.Post(ChainHeadEvent{event.Block})
+ }
+ }
+ // Fire the insertion events individually too
+ self.eventMux.Post(event)
+ }
+}
+
+func (self *BlockChain) update() {
+ futureTimer := time.Tick(5 * time.Second)
+ for {
+ select {
+ case <-futureTimer:
+ self.procFutureBlocks()
+ case <-self.quit:
+ return
+ }
+ }
+}
+
+func blockErr(block *types.Block, err error) {
+ if glog.V(logger.Error) {
+ glog.Errorf("Bad block #%v (%s)\n", block.Number(), block.Hash().Hex())
+ glog.Errorf(" %v", err)
+ }
+}
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
new file mode 100644
index 000000000..8ddc5032b
--- /dev/null
+++ b/core/blockchain_test.go
@@ -0,0 +1,954 @@
+// Copyright 2014 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 (
+ "fmt"
+ "math/big"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "testing"
+
+ "github.com/ethereum/ethash"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/ethereum/go-ethereum/pow"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/hashicorp/golang-lru"
+)
+
+func init() {
+ runtime.GOMAXPROCS(runtime.NumCPU())
+}
+
+func thePow() pow.PoW {
+ pow, _ := ethash.NewForTesting()
+ return pow
+}
+
+func theBlockChain(db ethdb.Database, t *testing.T) *BlockChain {
+ var eventMux event.TypeMux
+ WriteTestNetGenesisBlock(db, 0)
+ blockchain, err := NewBlockChain(db, thePow(), &eventMux)
+ if err != nil {
+ t.Error("failed creating chainmanager:", err)
+ t.FailNow()
+ return nil
+ }
+ blockMan := NewBlockProcessor(db, nil, blockchain, &eventMux)
+ blockchain.SetProcessor(blockMan)
+
+ return blockchain
+}
+
+// Test fork of length N starting from block i
+func testFork(t *testing.T, processor *BlockProcessor, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
+ // Copy old chain up to #i into a new db
+ db, processor2, err := newCanonical(i, full)
+ if err != nil {
+ t.Fatal("could not make new canonical in testFork", err)
+ }
+ // Assert the chains have the same header/block at #i
+ var hash1, hash2 common.Hash
+ if full {
+ hash1 = processor.bc.GetBlockByNumber(uint64(i)).Hash()
+ hash2 = processor2.bc.GetBlockByNumber(uint64(i)).Hash()
+ } else {
+ hash1 = processor.bc.GetHeaderByNumber(uint64(i)).Hash()
+ hash2 = processor2.bc.GetHeaderByNumber(uint64(i)).Hash()
+ }
+ if hash1 != hash2 {
+ t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1)
+ }
+ // Extend the newly created chain
+ var (
+ blockChainB []*types.Block
+ headerChainB []*types.Header
+ )
+ if full {
+ blockChainB = makeBlockChain(processor2.bc.CurrentBlock(), n, db, forkSeed)
+ if _, err := processor2.bc.InsertChain(blockChainB); err != nil {
+ t.Fatalf("failed to insert forking chain: %v", err)
+ }
+ } else {
+ headerChainB = makeHeaderChain(processor2.bc.CurrentHeader(), n, db, forkSeed)
+ if _, err := processor2.bc.InsertHeaderChain(headerChainB, 1); err != nil {
+ t.Fatalf("failed to insert forking chain: %v", err)
+ }
+ }
+ // Sanity check that the forked chain can be imported into the original
+ var tdPre, tdPost *big.Int
+
+ if full {
+ tdPre = processor.bc.GetTd(processor.bc.CurrentBlock().Hash())
+ if err := testBlockChainImport(blockChainB, processor); err != nil {
+ t.Fatalf("failed to import forked block chain: %v", err)
+ }
+ tdPost = processor.bc.GetTd(blockChainB[len(blockChainB)-1].Hash())
+ } else {
+ tdPre = processor.bc.GetTd(processor.bc.CurrentHeader().Hash())
+ if err := testHeaderChainImport(headerChainB, processor); err != nil {
+ t.Fatalf("failed to import forked header chain: %v", err)
+ }
+ tdPost = processor.bc.GetTd(headerChainB[len(headerChainB)-1].Hash())
+ }
+ // Compare the total difficulties of the chains
+ comparator(tdPre, tdPost)
+}
+
+func printChain(bc *BlockChain) {
+ for i := bc.CurrentBlock().Number().Uint64(); i > 0; i-- {
+ b := bc.GetBlockByNumber(uint64(i))
+ fmt.Printf("\t%x %v\n", b.Hash(), b.Difficulty())
+ }
+}
+
+// testBlockChainImport tries to process a chain of blocks, writing them into
+// the database if successful.
+func testBlockChainImport(chain []*types.Block, processor *BlockProcessor) error {
+ for _, block := range chain {
+ // Try and process the block
+ if _, _, err := processor.Process(block); err != nil {
+ if IsKnownBlockErr(err) {
+ continue
+ }
+ return err
+ }
+ // Manually insert the block into the database, but don't reorganize (allows subsequent testing)
+ processor.bc.mu.Lock()
+ WriteTd(processor.chainDb, block.Hash(), new(big.Int).Add(block.Difficulty(), processor.bc.GetTd(block.ParentHash())))
+ WriteBlock(processor.chainDb, block)
+ processor.bc.mu.Unlock()
+ }
+ return nil
+}
+
+// testHeaderChainImport tries to process a chain of header, writing them into
+// the database if successful.
+func testHeaderChainImport(chain []*types.Header, processor *BlockProcessor) error {
+ for _, header := range chain {
+ // Try and validate the header
+ if err := processor.ValidateHeader(header, false, false); err != nil {
+ return err
+ }
+ // Manually insert the header into the database, but don't reorganize (allows subsequent testing)
+ processor.bc.mu.Lock()
+ WriteTd(processor.chainDb, header.Hash(), new(big.Int).Add(header.Difficulty, processor.bc.GetTd(header.ParentHash)))
+ WriteHeader(processor.chainDb, header)
+ processor.bc.mu.Unlock()
+ }
+ return nil
+}
+
+func loadChain(fn string, t *testing.T) (types.Blocks, error) {
+ fh, err := os.OpenFile(filepath.Join("..", "_data", fn), os.O_RDONLY, os.ModePerm)
+ if err != nil {
+ return nil, err
+ }
+ defer fh.Close()
+
+ var chain types.Blocks
+ if err := rlp.Decode(fh, &chain); err != nil {
+ return nil, err
+ }
+
+ return chain, nil
+}
+
+func insertChain(done chan bool, blockchain *BlockChain, chain types.Blocks, t *testing.T) {
+ _, err := blockchain.InsertChain(chain)
+ if err != nil {
+ fmt.Println(err)
+ t.FailNow()
+ }
+ done <- true
+}
+
+func TestLastBlock(t *testing.T) {
+ db, _ := ethdb.NewMemDatabase()
+
+ bchain := theBlockChain(db, t)
+ block := makeBlockChain(bchain.CurrentBlock(), 1, db, 0)[0]
+ bchain.insert(block)
+ if block.Hash() != GetHeadBlockHash(db) {
+ t.Errorf("Write/Get HeadBlockHash failed")
+ }
+}
+
+// Tests that given a starting canonical chain of a given size, it can be extended
+// with various length chains.
+func TestExtendCanonicalHeaders(t *testing.T) { testExtendCanonical(t, false) }
+func TestExtendCanonicalBlocks(t *testing.T) { testExtendCanonical(t, true) }
+
+func testExtendCanonical(t *testing.T, full bool) {
+ length := 5
+
+ // Make first chain starting from genesis
+ _, processor, err := newCanonical(length, full)
+ if err != nil {
+ t.Fatalf("failed to make new canonical chain: %v", err)
+ }
+ // Define the difficulty comparator
+ better := func(td1, td2 *big.Int) {
+ if td2.Cmp(td1) <= 0 {
+ t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
+ }
+ }
+ // Start fork from current height
+ testFork(t, processor, length, 1, full, better)
+ testFork(t, processor, length, 2, full, better)
+ testFork(t, processor, length, 5, full, better)
+ testFork(t, processor, length, 10, full, better)
+}
+
+// Tests that given a starting canonical chain of a given size, creating shorter
+// forks do not take canonical ownership.
+func TestShorterForkHeaders(t *testing.T) { testShorterFork(t, false) }
+func TestShorterForkBlocks(t *testing.T) { testShorterFork(t, true) }
+
+func testShorterFork(t *testing.T, full bool) {
+ length := 10
+
+ // Make first chain starting from genesis
+ _, processor, err := newCanonical(length, full)
+ if err != nil {
+ t.Fatalf("failed to make new canonical chain: %v", err)
+ }
+ // Define the difficulty comparator
+ worse := func(td1, td2 *big.Int) {
+ if td2.Cmp(td1) >= 0 {
+ t.Errorf("total difficulty mismatch: have %v, expected less than %v", td2, td1)
+ }
+ }
+ // Sum of numbers must be less than `length` for this to be a shorter fork
+ testFork(t, processor, 0, 3, full, worse)
+ testFork(t, processor, 0, 7, full, worse)
+ testFork(t, processor, 1, 1, full, worse)
+ testFork(t, processor, 1, 7, full, worse)
+ testFork(t, processor, 5, 3, full, worse)
+ testFork(t, processor, 5, 4, full, worse)
+}
+
+// Tests that given a starting canonical chain of a given size, creating longer
+// forks do take canonical ownership.
+func TestLongerForkHeaders(t *testing.T) { testLongerFork(t, false) }
+func TestLongerForkBlocks(t *testing.T) { testLongerFork(t, true) }
+
+func testLongerFork(t *testing.T, full bool) {
+ length := 10
+
+ // Make first chain starting from genesis
+ _, processor, err := newCanonical(length, full)
+ if err != nil {
+ t.Fatalf("failed to make new canonical chain: %v", err)
+ }
+ // Define the difficulty comparator
+ better := func(td1, td2 *big.Int) {
+ if td2.Cmp(td1) <= 0 {
+ t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
+ }
+ }
+ // Sum of numbers must be greater than `length` for this to be a longer fork
+ testFork(t, processor, 0, 11, full, better)
+ testFork(t, processor, 0, 15, full, better)
+ testFork(t, processor, 1, 10, full, better)
+ testFork(t, processor, 1, 12, full, better)
+ testFork(t, processor, 5, 6, full, better)
+ testFork(t, processor, 5, 8, full, better)
+}
+
+// Tests that given a starting canonical chain of a given size, creating equal
+// forks do take canonical ownership.
+func TestEqualForkHeaders(t *testing.T) { testEqualFork(t, false) }
+func TestEqualForkBlocks(t *testing.T) { testEqualFork(t, true) }
+
+func testEqualFork(t *testing.T, full bool) {
+ length := 10
+
+ // Make first chain starting from genesis
+ _, processor, err := newCanonical(length, full)
+ if err != nil {
+ t.Fatalf("failed to make new canonical chain: %v", err)
+ }
+ // Define the difficulty comparator
+ equal := func(td1, td2 *big.Int) {
+ if td2.Cmp(td1) != 0 {
+ t.Errorf("total difficulty mismatch: have %v, want %v", td2, td1)
+ }
+ }
+ // Sum of numbers must be equal to `length` for this to be an equal fork
+ testFork(t, processor, 0, 10, full, equal)
+ testFork(t, processor, 1, 9, full, equal)
+ testFork(t, processor, 2, 8, full, equal)
+ testFork(t, processor, 5, 5, full, equal)
+ testFork(t, processor, 6, 4, full, equal)
+ testFork(t, processor, 9, 1, full, equal)
+}
+
+// Tests that chains missing links do not get accepted by the processor.
+func TestBrokenHeaderChain(t *testing.T) { testBrokenChain(t, false) }
+func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) }
+
+func testBrokenChain(t *testing.T, full bool) {
+ // Make chain starting from genesis
+ db, processor, err := newCanonical(10, full)
+ if err != nil {
+ t.Fatalf("failed to make new canonical chain: %v", err)
+ }
+ // Create a forked chain, and try to insert with a missing link
+ if full {
+ chain := makeBlockChain(processor.bc.CurrentBlock(), 5, db, forkSeed)[1:]
+ if err := testBlockChainImport(chain, processor); err == nil {
+ t.Errorf("broken block chain not reported")
+ }
+ } else {
+ chain := makeHeaderChain(processor.bc.CurrentHeader(), 5, db, forkSeed)[1:]
+ if err := testHeaderChainImport(chain, processor); err == nil {
+ t.Errorf("broken header chain not reported")
+ }
+ }
+}
+
+func TestChainInsertions(t *testing.T) {
+ t.Skip("Skipped: outdated test files")
+
+ db, _ := ethdb.NewMemDatabase()
+
+ chain1, err := loadChain("valid1", t)
+ if err != nil {
+ fmt.Println(err)
+ t.FailNow()
+ }
+
+ chain2, err := loadChain("valid2", t)
+ if err != nil {
+ fmt.Println(err)
+ t.FailNow()
+ }
+
+ blockchain := theBlockChain(db, t)
+
+ const max = 2
+ done := make(chan bool, max)
+
+ go insertChain(done, blockchain, chain1, t)
+ go insertChain(done, blockchain, chain2, t)
+
+ for i := 0; i < max; i++ {
+ <-done
+ }
+
+ if chain2[len(chain2)-1].Hash() != blockchain.CurrentBlock().Hash() {
+ t.Error("chain2 is canonical and shouldn't be")
+ }
+
+ if chain1[len(chain1)-1].Hash() != blockchain.CurrentBlock().Hash() {
+ t.Error("chain1 isn't canonical and should be")
+ }
+}
+
+func TestChainMultipleInsertions(t *testing.T) {
+ t.Skip("Skipped: outdated test files")
+
+ db, _ := ethdb.NewMemDatabase()
+
+ const max = 4
+ chains := make([]types.Blocks, max)
+ var longest int
+ for i := 0; i < max; i++ {
+ var err error
+ name := "valid" + strconv.Itoa(i+1)
+ chains[i], err = loadChain(name, t)
+ if len(chains[i]) >= len(chains[longest]) {
+ longest = i
+ }
+ fmt.Println("loaded", name, "with a length of", len(chains[i]))
+ if err != nil {
+ fmt.Println(err)
+ t.FailNow()
+ }
+ }
+
+ blockchain := theBlockChain(db, t)
+
+ done := make(chan bool, max)
+ for i, chain := range chains {
+ // XXX the go routine would otherwise reference the same (chain[3]) variable and fail
+ i := i
+ chain := chain
+ go func() {
+ insertChain(done, blockchain, chain, t)
+ fmt.Println(i, "done")
+ }()
+ }
+
+ for i := 0; i < max; i++ {
+ <-done
+ }
+
+ if chains[longest][len(chains[longest])-1].Hash() != blockchain.CurrentBlock().Hash() {
+ t.Error("Invalid canonical chain")
+ }
+}
+
+type bproc struct{}
+
+func (bproc) Process(*types.Block) (vm.Logs, types.Receipts, error) { return nil, nil, nil }
+func (bproc) ValidateHeader(*types.Header, bool, bool) error { return nil }
+func (bproc) ValidateHeaderWithParent(*types.Header, *types.Header, bool, bool) error { return nil }
+
+func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header {
+ blocks := makeBlockChainWithDiff(genesis, d, seed)
+ headers := make([]*types.Header, len(blocks))
+ for i, block := range blocks {
+ headers[i] = block.Header()
+ }
+ return headers
+}
+
+func makeBlockChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block {
+ var chain []*types.Block
+ for i, difficulty := range d {
+ header := &types.Header{
+ Coinbase: common.Address{seed},
+ Number: big.NewInt(int64(i + 1)),
+ Difficulty: big.NewInt(int64(difficulty)),
+ UncleHash: types.EmptyUncleHash,
+ TxHash: types.EmptyRootHash,
+ ReceiptHash: types.EmptyRootHash,
+ }
+ if i == 0 {
+ header.ParentHash = genesis.Hash()
+ } else {
+ header.ParentHash = chain[i-1].Hash()
+ }
+ block := types.NewBlockWithHeader(header)
+ chain = append(chain, block)
+ }
+ return chain
+}
+
+func chm(genesis *types.Block, db ethdb.Database) *BlockChain {
+ var eventMux event.TypeMux
+ bc := &BlockChain{chainDb: db, genesisBlock: genesis, eventMux: &eventMux, pow: FakePow{}, rand: rand.New(rand.NewSource(0))}
+ bc.headerCache, _ = lru.New(100)
+ bc.bodyCache, _ = lru.New(100)
+ bc.bodyRLPCache, _ = lru.New(100)
+ bc.tdCache, _ = lru.New(100)
+ bc.blockCache, _ = lru.New(100)
+ bc.futureBlocks, _ = lru.New(100)
+ bc.processor = bproc{}
+ bc.ResetWithGenesisBlock(genesis)
+
+ return bc
+}
+
+// Tests that reorganizing a long difficult chain after a short easy one
+// overwrites the canonical numbers and links in the database.
+func TestReorgLongHeaders(t *testing.T) { testReorgLong(t, false) }
+func TestReorgLongBlocks(t *testing.T) { testReorgLong(t, true) }
+
+func testReorgLong(t *testing.T, full bool) {
+ testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10, full)
+}
+
+// Tests that reorganizing a short difficult chain after a long easy one
+// overwrites the canonical numbers and links in the database.
+func TestReorgShortHeaders(t *testing.T) { testReorgShort(t, false) }
+func TestReorgShortBlocks(t *testing.T) { testReorgShort(t, true) }
+
+func testReorgShort(t *testing.T, full bool) {
+ testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11, full)
+}
+
+func testReorg(t *testing.T, first, second []int, td int64, full bool) {
+ // Create a pristine block chain
+ db, _ := ethdb.NewMemDatabase()
+ genesis, _ := WriteTestNetGenesisBlock(db, 0)
+ bc := chm(genesis, db)
+
+ // Insert an easy and a difficult chain afterwards
+ if full {
+ bc.InsertChain(makeBlockChainWithDiff(genesis, first, 11))
+ bc.InsertChain(makeBlockChainWithDiff(genesis, second, 22))
+ } else {
+ bc.InsertHeaderChain(makeHeaderChainWithDiff(genesis, first, 11), 1)
+ bc.InsertHeaderChain(makeHeaderChainWithDiff(genesis, second, 22), 1)
+ }
+ // Check that the chain is valid number and link wise
+ if full {
+ prev := bc.CurrentBlock()
+ for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) {
+ if prev.ParentHash() != block.Hash() {
+ t.Errorf("parent block hash mismatch: have %x, want %x", prev.ParentHash(), block.Hash())
+ }
+ }
+ } else {
+ prev := bc.CurrentHeader()
+ for header := bc.GetHeaderByNumber(bc.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, bc.GetHeaderByNumber(header.Number.Uint64()-1) {
+ if prev.ParentHash != header.Hash() {
+ t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash())
+ }
+ }
+ }
+ // Make sure the chain total difficulty is the correct one
+ want := new(big.Int).Add(genesis.Difficulty(), big.NewInt(td))
+ if full {
+ if have := bc.GetTd(bc.CurrentBlock().Hash()); have.Cmp(want) != 0 {
+ t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
+ }
+ } else {
+ if have := bc.GetTd(bc.CurrentHeader().Hash()); have.Cmp(want) != 0 {
+ t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
+ }
+ }
+}
+
+// Tests that the insertion functions detect banned hashes.
+func TestBadHeaderHashes(t *testing.T) { testBadHashes(t, false) }
+func TestBadBlockHashes(t *testing.T) { testBadHashes(t, true) }
+
+func testBadHashes(t *testing.T, full bool) {
+ // Create a pristine block chain
+ db, _ := ethdb.NewMemDatabase()
+ genesis, _ := WriteTestNetGenesisBlock(db, 0)
+ bc := chm(genesis, db)
+
+ // Create a chain, ban a hash and try to import
+ var err error
+ if full {
+ blocks := makeBlockChainWithDiff(genesis, []int{1, 2, 4}, 10)
+ BadHashes[blocks[2].Header().Hash()] = true
+ _, err = bc.InsertChain(blocks)
+ } else {
+ headers := makeHeaderChainWithDiff(genesis, []int{1, 2, 4}, 10)
+ BadHashes[headers[2].Hash()] = true
+ _, err = bc.InsertHeaderChain(headers, 1)
+ }
+ if !IsBadHashError(err) {
+ t.Errorf("error mismatch: want: BadHashError, have: %v", err)
+ }
+}
+
+// Tests that bad hashes are detected on boot, and the chan rolled back to a
+// good state prior to the bad hash.
+func TestReorgBadHeaderHashes(t *testing.T) { testReorgBadHashes(t, false) }
+func TestReorgBadBlockHashes(t *testing.T) { testReorgBadHashes(t, true) }
+
+func testReorgBadHashes(t *testing.T, full bool) {
+ // Create a pristine block chain
+ db, _ := ethdb.NewMemDatabase()
+ genesis, _ := WriteTestNetGenesisBlock(db, 0)
+ bc := chm(genesis, db)
+
+ // Create a chain, import and ban aferwards
+ headers := makeHeaderChainWithDiff(genesis, []int{1, 2, 3, 4}, 10)
+ blocks := makeBlockChainWithDiff(genesis, []int{1, 2, 3, 4}, 10)
+
+ if full {
+ if _, err := bc.InsertChain(blocks); err != nil {
+ t.Fatalf("failed to import blocks: %v", err)
+ }
+ if bc.CurrentBlock().Hash() != blocks[3].Hash() {
+ t.Errorf("last block hash mismatch: have: %x, want %x", bc.CurrentBlock().Hash(), blocks[3].Header().Hash())
+ }
+ BadHashes[blocks[3].Header().Hash()] = true
+ defer func() { delete(BadHashes, blocks[3].Header().Hash()) }()
+ } else {
+ if _, err := bc.InsertHeaderChain(headers, 1); err != nil {
+ t.Fatalf("failed to import headers: %v", err)
+ }
+ if bc.CurrentHeader().Hash() != headers[3].Hash() {
+ t.Errorf("last header hash mismatch: have: %x, want %x", bc.CurrentHeader().Hash(), headers[3].Hash())
+ }
+ BadHashes[headers[3].Hash()] = true
+ defer func() { delete(BadHashes, headers[3].Hash()) }()
+ }
+ // Create a new chain manager and check it rolled back the state
+ ncm, err := NewBlockChain(db, FakePow{}, new(event.TypeMux))
+ if err != nil {
+ t.Fatalf("failed to create new chain manager: %v", err)
+ }
+ if full {
+ if ncm.CurrentBlock().Hash() != blocks[2].Header().Hash() {
+ t.Errorf("last block hash mismatch: have: %x, want %x", ncm.CurrentBlock().Hash(), blocks[2].Header().Hash())
+ }
+ if blocks[2].Header().GasLimit.Cmp(ncm.GasLimit()) != 0 {
+ t.Errorf("last block gasLimit mismatch: have: %x, want %x", ncm.GasLimit(), blocks[2].Header().GasLimit)
+ }
+ } else {
+ if ncm.CurrentHeader().Hash() != headers[2].Hash() {
+ t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash())
+ }
+ }
+}
+
+// Tests chain insertions in the face of one entity containing an invalid nonce.
+func TestHeadersInsertNonceError(t *testing.T) { testInsertNonceError(t, false) }
+func TestBlocksInsertNonceError(t *testing.T) { testInsertNonceError(t, true) }
+
+func testInsertNonceError(t *testing.T, full bool) {
+ for i := 1; i < 25 && !t.Failed(); i++ {
+ // Create a pristine chain and database
+ db, processor, err := newCanonical(0, full)
+ if err != nil {
+ t.Fatalf("failed to create pristine chain: %v", err)
+ }
+ bc := processor.bc
+
+ // Create and insert a chain with a failing nonce
+ var (
+ failAt int
+ failRes int
+ failNum uint64
+ failHash common.Hash
+ )
+ if full {
+ blocks := makeBlockChain(processor.bc.CurrentBlock(), i, db, 0)
+
+ failAt = rand.Int() % len(blocks)
+ failNum = blocks[failAt].NumberU64()
+ failHash = blocks[failAt].Hash()
+
+ processor.bc.pow = failPow{failNum}
+ processor.Pow = failPow{failNum}
+
+ failRes, err = processor.bc.InsertChain(blocks)
+ } else {
+ headers := makeHeaderChain(processor.bc.CurrentHeader(), i, db, 0)
+
+ failAt = rand.Int() % len(headers)
+ failNum = headers[failAt].Number.Uint64()
+ failHash = headers[failAt].Hash()
+
+ processor.bc.pow = failPow{failNum}
+ processor.Pow = failPow{failNum}
+
+ failRes, err = processor.bc.InsertHeaderChain(headers, 1)
+ }
+ // Check that the returned error indicates the nonce failure.
+ if failRes != failAt {
+ t.Errorf("test %d: failure index mismatch: have %d, want %d", i, failRes, failAt)
+ }
+ if !IsBlockNonceErr(err) {
+ t.Fatalf("test %d: error mismatch: have %v, want nonce error", i, err)
+ }
+ nerr := err.(*BlockNonceErr)
+ if nerr.Number.Uint64() != failNum {
+ t.Errorf("test %d: number mismatch: have %v, want %v", i, nerr.Number, failNum)
+ }
+ if nerr.Hash != failHash {
+ t.Errorf("test %d: hash mismatch: have %x, want %x", i, nerr.Hash[:4], failHash[:4])
+ }
+ // Check that all no blocks after the failing block have been inserted.
+ for j := 0; j < i-failAt; j++ {
+ if full {
+ if block := bc.GetBlockByNumber(failNum + uint64(j)); block != nil {
+ t.Errorf("test %d: invalid block in chain: %v", i, block)
+ }
+ } else {
+ if header := bc.GetHeaderByNumber(failNum + uint64(j)); header != nil {
+ t.Errorf("test %d: invalid header in chain: %v", i, header)
+ }
+ }
+ }
+ }
+}
+
+// Tests that fast importing a block chain produces the same chain data as the
+// classical full block processing.
+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)
+ genesis = GenesisBlockForTesting(gendb, address, funds)
+ )
+ blocks, receipts := GenerateChain(genesis, gendb, 1024, func(i int, block *BlockGen) {
+ block.SetCoinbase(common.Address{0x00})
+
+ // If the block number is multiple of 3, send a few bonus transactions to the miner
+ if i%3 == 2 {
+ for j := 0; j < i%4+1; j++ {
+ tx, err := types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key)
+ if err != nil {
+ panic(err)
+ }
+ block.AddTx(tx)
+ }
+ }
+ // If the block number is a multiple of 5, add a few bonus uncles to the block
+ if i%5 == 5 {
+ block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 1).Hash(), Number: big.NewInt(int64(i - 1))})
+ }
+ })
+ // Import the chain as an archive node for the comparison baseline
+ archiveDb, _ := ethdb.NewMemDatabase()
+ WriteGenesisBlockForTesting(archiveDb, GenesisAccount{address, funds})
+
+ archive, _ := NewBlockChain(archiveDb, FakePow{}, new(event.TypeMux))
+ archive.SetProcessor(NewBlockProcessor(archiveDb, FakePow{}, archive, new(event.TypeMux)))
+
+ if n, err := archive.InsertChain(blocks); err != nil {
+ t.Fatalf("failed to process block %d: %v", n, err)
+ }
+ // Fast import the chain as a non-archive node to test
+ fastDb, _ := ethdb.NewMemDatabase()
+ WriteGenesisBlockForTesting(fastDb, GenesisAccount{address, funds})
+ fast, _ := NewBlockChain(fastDb, FakePow{}, new(event.TypeMux))
+ fast.SetProcessor(NewBlockProcessor(fastDb, FakePow{}, fast, new(event.TypeMux)))
+
+ headers := make([]*types.Header, len(blocks))
+ for i, block := range blocks {
+ headers[i] = block.Header()
+ }
+ if n, err := fast.InsertHeaderChain(headers, 1); err != nil {
+ t.Fatalf("failed to insert header %d: %v", n, err)
+ }
+ if n, err := fast.InsertReceiptChain(blocks, receipts); err != nil {
+ t.Fatalf("failed to insert receipt %d: %v", n, err)
+ }
+ // Iterate over all chain data components, and cross reference
+ for i := 0; i < len(blocks); i++ {
+ num, hash := blocks[i].NumberU64(), blocks[i].Hash()
+
+ if ftd, atd := fast.GetTd(hash), archive.GetTd(hash); ftd.Cmp(atd) != 0 {
+ t.Errorf("block #%d [%x]: td mismatch: have %v, want %v", num, hash, ftd, atd)
+ }
+ if fheader, aheader := fast.GetHeader(hash), archive.GetHeader(hash); fheader.Hash() != aheader.Hash() {
+ t.Errorf("block #%d [%x]: header mismatch: have %v, want %v", num, hash, fheader, aheader)
+ }
+ if fblock, ablock := fast.GetBlock(hash), archive.GetBlock(hash); fblock.Hash() != ablock.Hash() {
+ t.Errorf("block #%d [%x]: block mismatch: have %v, want %v", num, hash, fblock, ablock)
+ } else if types.DeriveSha(fblock.Transactions()) != types.DeriveSha(ablock.Transactions()) {
+ t.Errorf("block #%d [%x]: transactions mismatch: have %v, want %v", num, hash, fblock.Transactions(), ablock.Transactions())
+ } 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), GetBlockReceipts(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 {
+ t.Errorf("block #%d: canonical hash mismatch: have %v, want %v", i, fhash, ahash)
+ }
+ }
+}
+
+// Tests that various import methods move the chain head pointers to the correct
+// positions.
+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)
+ genesis = GenesisBlockForTesting(gendb, address, funds)
+ )
+ height := uint64(1024)
+ blocks, receipts := GenerateChain(genesis, gendb, int(height), nil)
+
+ // Configure a subchain to roll back
+ remove := []common.Hash{}
+ for _, block := range blocks[height/2:] {
+ remove = append(remove, block.Hash())
+ }
+ // Create a small assertion method to check the three heads
+ assert := func(t *testing.T, kind string, chain *BlockChain, header uint64, fast uint64, block uint64) {
+ if num := chain.CurrentBlock().NumberU64(); num != block {
+ t.Errorf("%s head block mismatch: have #%v, want #%v", kind, num, block)
+ }
+ if num := chain.CurrentFastBlock().NumberU64(); num != fast {
+ t.Errorf("%s head fast-block mismatch: have #%v, want #%v", kind, num, fast)
+ }
+ if num := chain.CurrentHeader().Number.Uint64(); num != header {
+ t.Errorf("%s head header mismatch: have #%v, want #%v", kind, num, header)
+ }
+ }
+ // Import the chain as an archive node and ensure all pointers are updated
+ archiveDb, _ := ethdb.NewMemDatabase()
+ WriteGenesisBlockForTesting(archiveDb, GenesisAccount{address, funds})
+
+ archive, _ := NewBlockChain(archiveDb, FakePow{}, new(event.TypeMux))
+ archive.SetProcessor(NewBlockProcessor(archiveDb, FakePow{}, archive, new(event.TypeMux)))
+
+ if n, err := archive.InsertChain(blocks); err != nil {
+ t.Fatalf("failed to process block %d: %v", n, err)
+ }
+ assert(t, "archive", archive, height, height, height)
+ archive.Rollback(remove)
+ 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()
+ WriteGenesisBlockForTesting(fastDb, GenesisAccount{address, funds})
+ fast, _ := NewBlockChain(fastDb, FakePow{}, new(event.TypeMux))
+ fast.SetProcessor(NewBlockProcessor(fastDb, FakePow{}, fast, new(event.TypeMux)))
+
+ headers := make([]*types.Header, len(blocks))
+ for i, block := range blocks {
+ headers[i] = block.Header()
+ }
+ if n, err := fast.InsertHeaderChain(headers, 1); err != nil {
+ t.Fatalf("failed to insert header %d: %v", n, err)
+ }
+ if n, err := fast.InsertReceiptChain(blocks, receipts); err != nil {
+ t.Fatalf("failed to insert receipt %d: %v", n, err)
+ }
+ assert(t, "fast", fast, height, height, 0)
+ fast.Rollback(remove)
+ 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()
+ WriteGenesisBlockForTesting(lightDb, GenesisAccount{address, funds})
+ light, _ := NewBlockChain(lightDb, FakePow{}, new(event.TypeMux))
+ light.SetProcessor(NewBlockProcessor(lightDb, FakePow{}, light, new(event.TypeMux)))
+
+ if n, err := light.InsertHeaderChain(headers, 1); err != nil {
+ t.Fatalf("failed to insert header %d: %v", n, err)
+ }
+ assert(t, "light", light, height, 0, 0)
+ light.Rollback(remove)
+ assert(t, "light", light, height/2, 0, 0)
+}
+
+// Tests that chain reorganizations handle transaction removals and reinsertions.
+func TestChainTxReorgs(t *testing.T) {
+ params.MinGasLimit = big.NewInt(125000) // Minimum the gas limit may ever be.
+ params.GenesisGasLimit = big.NewInt(3141592) // Gas limit of the Genesis block.
+
+ var (
+ key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
+ key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
+ addr1 = crypto.PubkeyToAddress(key1.PublicKey)
+ addr2 = crypto.PubkeyToAddress(key2.PublicKey)
+ addr3 = crypto.PubkeyToAddress(key3.PublicKey)
+ db, _ = ethdb.NewMemDatabase()
+ )
+ genesis := WriteGenesisBlockForTesting(db,
+ GenesisAccount{addr1, big.NewInt(1000000)},
+ GenesisAccount{addr2, big.NewInt(1000000)},
+ GenesisAccount{addr3, big.NewInt(1000000)},
+ )
+ // Create two transactions shared between the chains:
+ // - postponed: transaction included at a later block in the forked chain
+ // - swapped: transaction included at the same block number in the forked chain
+ postponed, _ := types.NewTransaction(0, addr1, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1)
+ swapped, _ := types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1)
+
+ // Create two transactions that will be dropped by the forked chain:
+ // - pastDrop: transaction dropped retroactively from a past block
+ // - freshDrop: transaction dropped exactly at the block where the reorg is detected
+ var pastDrop, freshDrop *types.Transaction
+
+ // Create three transactions that will be added in the forked chain:
+ // - pastAdd: transaction added before the reorganiztion is detected
+ // - freshAdd: transaction added at the exact block the reorg is detected
+ // - futureAdd: transaction added after the reorg has already finished
+ var pastAdd, freshAdd, futureAdd *types.Transaction
+
+ chain, _ := GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) {
+ switch i {
+ case 0:
+ pastDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2)
+
+ gen.AddTx(pastDrop) // This transaction will be dropped in the fork from below the split point
+ gen.AddTx(postponed) // This transaction will be postponed till block #3 in the fork
+
+ case 2:
+ freshDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2)
+
+ gen.AddTx(freshDrop) // This transaction will be dropped in the fork from exactly at the split point
+ gen.AddTx(swapped) // This transaction will be swapped out at the exact height
+
+ gen.OffsetTime(9) // Lower the block difficulty to simulate a weaker chain
+ }
+ })
+ // Import the chain. This runs all block validation rules.
+ evmux := &event.TypeMux{}
+ chainman, _ := NewBlockChain(db, FakePow{}, evmux)
+ chainman.SetProcessor(NewBlockProcessor(db, FakePow{}, chainman, evmux))
+ if i, err := chainman.InsertChain(chain); err != nil {
+ t.Fatalf("failed to insert original chain[%d]: %v", i, err)
+ }
+
+ // overwrite the old chain
+ chain, _ = GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) {
+ switch i {
+ case 0:
+ pastAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
+ gen.AddTx(pastAdd) // This transaction needs to be injected during reorg
+
+ case 2:
+ gen.AddTx(postponed) // This transaction was postponed from block #1 in the original chain
+ gen.AddTx(swapped) // This transaction was swapped from the exact current spot in the original chain
+
+ freshAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
+ gen.AddTx(freshAdd) // This transaction will be added exactly at reorg time
+
+ case 3:
+ futureAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
+ gen.AddTx(futureAdd) // This transaction will be added after a full reorg
+ }
+ })
+ if _, err := chainman.InsertChain(chain); err != nil {
+ t.Fatalf("failed to insert forked chain: %v", err)
+ }
+
+ // removed tx
+ for i, tx := range (types.Transactions{pastDrop, freshDrop}) {
+ if GetTransaction(db, tx.Hash()) != nil {
+ t.Errorf("drop %d: tx found while shouldn't have been", i)
+ }
+ if GetReceipt(db, tx.Hash()) != nil {
+ t.Errorf("drop %d: receipt found while shouldn't have been", i)
+ }
+ }
+ // added tx
+ for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) {
+ if GetTransaction(db, tx.Hash()) == nil {
+ t.Errorf("add %d: expected tx to be found", i)
+ }
+ if GetReceipt(db, tx.Hash()) == nil {
+ t.Errorf("add %d: expected receipt to be found", i)
+ }
+ }
+ // shared tx
+ for i, tx := range (types.Transactions{postponed, swapped}) {
+ if GetTransaction(db, tx.Hash()) == nil {
+ t.Errorf("share %d: expected tx to be found", i)
+ }
+ if GetReceipt(db, tx.Hash()) == nil {
+ t.Errorf("share %d: expected receipt to be found", i)
+ }
+ }
+}
diff --git a/core/chain_makers.go b/core/chain_makers.go
index 70233438d..56e37a0fc 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -17,6 +17,7 @@
package core
import (
+ "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
@@ -31,7 +32,7 @@ import (
// It returns true from Verify for any block.
type FakePow struct{}
-func (f FakePow) Search(block pow.Block, stop <-chan struct{}) (uint64, []byte) {
+func (f FakePow) Search(block pow.Block, stop <-chan struct{}, index int) (uint64, []byte) {
return 0, nil
}
func (f FakePow) Verify(block pow.Block) bool { return true }
@@ -53,7 +54,7 @@ type BlockGen struct {
header *types.Header
statedb *state.StateDB
- coinbase *state.StateObject
+ gasPool *GasPool
txs []*types.Transaction
receipts []*types.Receipt
uncles []*types.Header
@@ -62,15 +63,14 @@ type BlockGen struct {
// SetCoinbase sets the coinbase of the generated block.
// It can be called at most once.
func (b *BlockGen) SetCoinbase(addr common.Address) {
- if b.coinbase != nil {
+ if b.gasPool != nil {
if len(b.txs) > 0 {
panic("coinbase must be set before adding transactions")
}
panic("coinbase can only be set once")
}
b.header.Coinbase = addr
- b.coinbase = b.statedb.GetOrNewStateObject(addr)
- b.coinbase.SetGasLimit(b.header.GasLimit)
+ b.gasPool = new(GasPool).AddGas(b.header.GasLimit)
}
// SetExtra sets the extra data field of the generated block.
@@ -87,23 +87,32 @@ 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) {
- if b.coinbase == nil {
+ if b.gasPool == nil {
b.SetCoinbase(common.Address{})
}
- _, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.coinbase)
+ _, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.gasPool)
if err != nil {
panic(err)
}
- b.statedb.SyncIntermediate()
+ root := b.statedb.IntermediateRoot()
b.header.GasUsed.Add(b.header.GasUsed, gas)
- receipt := types.NewReceipt(b.statedb.Root().Bytes(), b.header.GasUsed)
+ receipt := types.NewReceipt(root.Bytes(), b.header.GasUsed)
logs := b.statedb.GetLogs(tx.Hash())
- receipt.SetLogs(logs)
+ receipt.Logs = logs
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
b.txs = append(b.txs, tx)
b.receipts = append(b.receipts, receipt)
}
+// AddUncheckedReceipts forcefully adds a receipts to the block without a
+// backing transaction.
+//
+// AddUncheckedReceipts will cause consensus failures when used during real
+// chain processing. This is best used in conjuction with raw block insertion.
+func (b *BlockGen) AddUncheckedReceipt(receipt *types.Receipt) {
+ b.receipts = append(b.receipts, receipt)
+}
+
// TxNonce returns the next valid transaction nonce for the
// account at addr. It panics if the account does not exist.
func (b *BlockGen) TxNonce(addr common.Address) uint64 {
@@ -152,28 +161,35 @@ func (b *BlockGen) OffsetTime(seconds int64) {
// and their coinbase will be the zero address.
//
// Blocks created by GenerateChain do not contain valid proof of work
-// values. Inserting them into ChainManager requires use of FakePow or
+// values. Inserting them into BlockChain requires use of FakePow or
// a similar non-validating proof of work implementation.
-func GenerateChain(parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) []*types.Block {
- statedb := state.New(parent.Root(), db)
- blocks := make(types.Blocks, n)
- genblock := func(i int, h *types.Header) *types.Block {
+func GenerateChain(parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
+ statedb, err := state.New(parent.Root(), db)
+ if err != nil {
+ panic(err)
+ }
+ blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
+ genblock := func(i int, h *types.Header) (*types.Block, types.Receipts) {
b := &BlockGen{parent: parent, i: i, chain: blocks, header: h, statedb: statedb}
if gen != nil {
gen(i, b)
}
AccumulateRewards(statedb, h, b.uncles)
- statedb.SyncIntermediate()
- h.Root = statedb.Root()
- return types.NewBlock(h, b.txs, b.uncles, b.receipts)
+ root, err := statedb.Commit()
+ if err != nil {
+ panic(fmt.Sprintf("state write error: %v", err))
+ }
+ h.Root = root
+ return types.NewBlock(h, b.txs, b.uncles, b.receipts), b.receipts
}
for i := 0; i < n; i++ {
header := makeHeader(parent, statedb)
- block := genblock(i, header)
+ block, receipt := genblock(i, header)
blocks[i] = block
+ receipts[i] = receipt
parent = block
}
- return blocks
+ return blocks, receipts
}
func makeHeader(parent *types.Block, state *state.StateDB) *types.Header {
@@ -184,7 +200,7 @@ func makeHeader(parent *types.Block, state *state.StateDB) *types.Header {
time = new(big.Int).Add(parent.Time(), big.NewInt(10)) // block time is fixed at 10 seconds
}
return &types.Header{
- Root: state.Root(),
+ Root: state.IntermediateRoot(),
ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(),
Difficulty: CalcDifficulty(time.Uint64(), new(big.Int).Sub(time, big.NewInt(10)).Uint64(), parent.Number(), parent.Difficulty()),
@@ -195,26 +211,51 @@ func makeHeader(parent *types.Block, state *state.StateDB) *types.Header {
}
}
-// newCanonical creates a new deterministic canonical chain by running
-// InsertChain on the result of makeChain.
-func newCanonical(n int, db ethdb.Database) (*BlockProcessor, error) {
+// 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(n int, full bool) (ethdb.Database, *BlockProcessor, error) {
+ // Create te new chain database
+ db, _ := ethdb.NewMemDatabase()
evmux := &event.TypeMux{}
- WriteTestNetGenesisBlock(db, 0)
- chainman, _ := NewChainManager(db, FakePow{}, evmux)
- bman := NewBlockProcessor(db, FakePow{}, chainman, evmux)
- bman.bc.SetProcessor(bman)
- parent := bman.bc.CurrentBlock()
+ // Initialize a fresh chain with only a genesis block
+ genesis, _ := WriteTestNetGenesisBlock(db, 0)
+
+ blockchain, _ := NewBlockChain(db, FakePow{}, evmux)
+ processor := NewBlockProcessor(db, FakePow{}, blockchain, evmux)
+ processor.bc.SetProcessor(processor)
+
+ // Create and inject the requested chain
if n == 0 {
- return bman, nil
+ return db, processor, nil
+ }
+ if full {
+ // Full block-chain requested
+ blocks := makeBlockChain(genesis, n, db, canonicalSeed)
+ _, err := blockchain.InsertChain(blocks)
+ return db, processor, err
}
- lchain := makeChain(parent, n, db, canonicalSeed)
- _, err := bman.bc.InsertChain(lchain)
- return bman, err
+ // Header-only chain requested
+ headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed)
+ _, err := blockchain.InsertHeaderChain(headers, 1)
+ return db, processor, err
}
-func makeChain(parent *types.Block, n int, db ethdb.Database, seed int) []*types.Block {
- return GenerateChain(parent, db, n, func(i int, b *BlockGen) {
+// makeHeaderChain creates a deterministic chain of headers rooted at parent.
+func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) []*types.Header {
+ blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, db, seed)
+ headers := make([]*types.Header, len(blocks))
+ for i, block := range blocks {
+ headers[i] = block.Header()
+ }
+ return headers
+}
+
+// makeBlockChain creates a deterministic chain of blocks rooted at parent.
+func makeBlockChain(parent *types.Block, n int, db ethdb.Database, seed int) []*types.Block {
+ blocks, _ := GenerateChain(parent, db, n, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
})
+ return blocks
}
diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go
index ac18e5e0b..7f47cf288 100644
--- a/core/chain_makers_test.go
+++ b/core/chain_makers_test.go
@@ -47,7 +47,7 @@ func ExampleGenerateChain() {
// This call generates a chain of 5 blocks. The function runs for
// each block and adds different features to gen based on the
// block index.
- chain := GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) {
+ chain, _ := GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) {
switch i {
case 0:
// In block 1, addr1 sends addr2 some ether.
@@ -77,14 +77,14 @@ func ExampleGenerateChain() {
// Import the chain. This runs all block validation rules.
evmux := &event.TypeMux{}
- chainman, _ := NewChainManager(db, FakePow{}, evmux)
+ chainman, _ := NewBlockChain(db, FakePow{}, evmux)
chainman.SetProcessor(NewBlockProcessor(db, FakePow{}, chainman, evmux))
if i, err := chainman.InsertChain(chain); err != nil {
fmt.Printf("insert error (block %d): %v\n", i, err)
return
}
- state := chainman.State()
+ state, _ := chainman.State()
fmt.Printf("last block: #%d\n", chainman.CurrentBlock().Number())
fmt.Println("balance of addr1:", state.GetBalance(addr1))
fmt.Println("balance of addr2:", state.GetBalance(addr2))
diff --git a/core/chain_manager.go b/core/chain_manager.go
deleted file mode 100644
index 383fce70c..000000000
--- a/core/chain_manager.go
+++ /dev/null
@@ -1,847 +0,0 @@
-// Copyright 2014 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 implements the Ethereum consensus protocol.
-package core
-
-import (
- "errors"
- "fmt"
- "io"
- "math/big"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/logger"
- "github.com/ethereum/go-ethereum/logger/glog"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/pow"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/hashicorp/golang-lru"
-)
-
-var (
- chainlogger = logger.NewLogger("CHAIN")
- jsonlogger = logger.NewJsonLogger()
-
- blockInsertTimer = metrics.NewTimer("chain/inserts")
-
- ErrNoGenesis = errors.New("Genesis not found in chain")
-)
-
-const (
- headerCacheLimit = 512
- bodyCacheLimit = 256
- tdCacheLimit = 1024
- blockCacheLimit = 256
- maxFutureBlocks = 256
- maxTimeFutureBlocks = 30
- checkpointLimit = 200
-)
-
-type ChainManager struct {
- //eth EthManager
- chainDb ethdb.Database
- processor types.BlockProcessor
- eventMux *event.TypeMux
- genesisBlock *types.Block
- // Last known total difficulty
- mu sync.RWMutex
- chainmu sync.RWMutex
- tsmu sync.RWMutex
-
- checkpoint int // checkpoint counts towards the new checkpoint
- td *big.Int
- currentBlock *types.Block
- currentGasLimit *big.Int
-
- headerCache *lru.Cache // Cache for the most recent block headers
- bodyCache *lru.Cache // Cache for the most recent block bodies
- bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
- tdCache *lru.Cache // Cache for the most recent block total difficulties
- blockCache *lru.Cache // Cache for the most recent entire blocks
- futureBlocks *lru.Cache // future blocks are blocks added for later processing
-
- quit chan struct{}
- running int32 // running must be called automically
- // procInterrupt must be atomically called
- procInterrupt int32 // interrupt signaler for block processing
- wg sync.WaitGroup
-
- pow pow.PoW
-}
-
-func NewChainManager(chainDb ethdb.Database, pow pow.PoW, mux *event.TypeMux) (*ChainManager, error) {
- headerCache, _ := lru.New(headerCacheLimit)
- bodyCache, _ := lru.New(bodyCacheLimit)
- bodyRLPCache, _ := lru.New(bodyCacheLimit)
- tdCache, _ := lru.New(tdCacheLimit)
- blockCache, _ := lru.New(blockCacheLimit)
- futureBlocks, _ := lru.New(maxFutureBlocks)
-
- bc := &ChainManager{
- chainDb: chainDb,
- eventMux: mux,
- quit: make(chan struct{}),
- headerCache: headerCache,
- bodyCache: bodyCache,
- bodyRLPCache: bodyRLPCache,
- tdCache: tdCache,
- blockCache: blockCache,
- futureBlocks: futureBlocks,
- pow: pow,
- }
-
- bc.genesisBlock = bc.GetBlockByNumber(0)
- if bc.genesisBlock == nil {
- reader, err := NewDefaultGenesisReader()
- if err != nil {
- return nil, err
- }
- bc.genesisBlock, err = WriteGenesisBlock(chainDb, reader)
- if err != nil {
- return nil, err
- }
- glog.V(logger.Info).Infoln("WARNING: Wrote default ethereum genesis block")
- }
- if err := bc.setLastState(); err != nil {
- return nil, err
- }
- // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
- for hash, _ := range BadHashes {
- if block := bc.GetBlock(hash); block != nil {
- glog.V(logger.Error).Infof("Found bad hash. Reorganising chain to state %x\n", block.ParentHash().Bytes()[:4])
- block = bc.GetBlock(block.ParentHash())
- if block == nil {
- glog.Fatal("Unable to complete. Parent block not found. Corrupted DB?")
- }
- bc.SetHead(block)
-
- glog.V(logger.Error).Infoln("Chain reorg was successfull. Resuming normal operation")
- }
- }
- // Take ownership of this particular state
- go bc.update()
- return bc, nil
-}
-
-func (bc *ChainManager) SetHead(head *types.Block) {
- bc.mu.Lock()
- defer bc.mu.Unlock()
-
- for block := bc.currentBlock; block != nil && block.Hash() != head.Hash(); block = bc.GetBlock(block.ParentHash()) {
- DeleteBlock(bc.chainDb, block.Hash())
- }
- bc.headerCache.Purge()
- bc.bodyCache.Purge()
- bc.bodyRLPCache.Purge()
- bc.blockCache.Purge()
- bc.futureBlocks.Purge()
-
- bc.currentBlock = head
- bc.setTotalDifficulty(bc.GetTd(head.Hash()))
- bc.insert(head)
- bc.setLastState()
-}
-
-func (self *ChainManager) Td() *big.Int {
- self.mu.RLock()
- defer self.mu.RUnlock()
-
- return new(big.Int).Set(self.td)
-}
-
-func (self *ChainManager) GasLimit() *big.Int {
- self.mu.RLock()
- defer self.mu.RUnlock()
-
- return self.currentBlock.GasLimit()
-}
-
-func (self *ChainManager) LastBlockHash() common.Hash {
- self.mu.RLock()
- defer self.mu.RUnlock()
-
- return self.currentBlock.Hash()
-}
-
-func (self *ChainManager) CurrentBlock() *types.Block {
- self.mu.RLock()
- defer self.mu.RUnlock()
-
- return self.currentBlock
-}
-
-func (self *ChainManager) Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash) {
- self.mu.RLock()
- defer self.mu.RUnlock()
-
- return new(big.Int).Set(self.td), self.currentBlock.Hash(), self.genesisBlock.Hash()
-}
-
-func (self *ChainManager) SetProcessor(proc types.BlockProcessor) {
- self.processor = proc
-}
-
-func (self *ChainManager) State() *state.StateDB {
- return state.New(self.CurrentBlock().Root(), self.chainDb)
-}
-
-func (bc *ChainManager) recover() bool {
- data, _ := bc.chainDb.Get([]byte("checkpoint"))
- if len(data) != 0 {
- block := bc.GetBlock(common.BytesToHash(data))
- if block != nil {
- if err := WriteCanonicalHash(bc.chainDb, block.Hash(), block.NumberU64()); err != nil {
- glog.Fatalf("failed to write database head number: %v", err)
- }
- if err := WriteHeadBlockHash(bc.chainDb, block.Hash()); err != nil {
- glog.Fatalf("failed to write database head hash: %v", err)
- }
- bc.currentBlock = block
- return true
- }
- }
- return false
-}
-
-func (bc *ChainManager) setLastState() error {
- head := GetHeadBlockHash(bc.chainDb)
- if head != (common.Hash{}) {
- block := bc.GetBlock(head)
- if block != nil {
- bc.currentBlock = block
- } else {
- glog.Infof("LastBlock (%x) not found. Recovering...\n", head)
- if bc.recover() {
- glog.Infof("Recover successful")
- } else {
- glog.Fatalf("Recover failed. Please report")
- }
- }
- } else {
- bc.Reset()
- }
- bc.td = bc.GetTd(bc.currentBlock.Hash())
- bc.currentGasLimit = CalcGasLimit(bc.currentBlock)
-
- if glog.V(logger.Info) {
- glog.Infof("Last block (#%v) %x TD=%v\n", bc.currentBlock.Number(), bc.currentBlock.Hash(), bc.td)
- }
-
- return nil
-}
-
-// Reset purges the entire blockchain, restoring it to its genesis state.
-func (bc *ChainManager) Reset() {
- bc.ResetWithGenesisBlock(bc.genesisBlock)
-}
-
-// ResetWithGenesisBlock purges the entire blockchain, restoring it to the
-// specified genesis state.
-func (bc *ChainManager) ResetWithGenesisBlock(genesis *types.Block) {
- bc.mu.Lock()
- defer bc.mu.Unlock()
-
- // Dump the entire block chain and purge the caches
- for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.ParentHash()) {
- DeleteBlock(bc.chainDb, block.Hash())
- }
- bc.headerCache.Purge()
- bc.bodyCache.Purge()
- bc.bodyRLPCache.Purge()
- bc.blockCache.Purge()
- bc.futureBlocks.Purge()
-
- // Prepare the genesis block and reinitialize the chain
- if err := WriteTd(bc.chainDb, genesis.Hash(), genesis.Difficulty()); err != nil {
- glog.Fatalf("failed to write genesis block TD: %v", err)
- }
- if err := WriteBlock(bc.chainDb, genesis); err != nil {
- glog.Fatalf("failed to write genesis block: %v", err)
- }
- bc.genesisBlock = genesis
- bc.insert(bc.genesisBlock)
- bc.currentBlock = bc.genesisBlock
- bc.setTotalDifficulty(genesis.Difficulty())
-}
-
-// Export writes the active chain to the given writer.
-func (self *ChainManager) Export(w io.Writer) error {
- if err := self.ExportN(w, uint64(0), self.currentBlock.NumberU64()); err != nil {
- return err
- }
- return nil
-}
-
-// ExportN writes a subset of the active chain to the given writer.
-func (self *ChainManager) ExportN(w io.Writer, first uint64, last uint64) error {
- self.mu.RLock()
- defer self.mu.RUnlock()
-
- if first > last {
- return fmt.Errorf("export failed: first (%d) is greater than last (%d)", first, last)
- }
-
- glog.V(logger.Info).Infof("exporting %d blocks...\n", last-first+1)
-
- for nr := first; nr <= last; nr++ {
- block := self.GetBlockByNumber(nr)
- if block == nil {
- return fmt.Errorf("export failed on #%d: not found", nr)
- }
-
- if err := block.EncodeRLP(w); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// insert injects a block into the current chain block chain. Note, this function
-// assumes that the `mu` mutex is held!
-func (bc *ChainManager) insert(block *types.Block) {
- // Add the block to the canonical chain number scheme and mark as the head
- if err := WriteCanonicalHash(bc.chainDb, block.Hash(), block.NumberU64()); err != nil {
- glog.Fatalf("failed to insert block number: %v", err)
- }
- if err := WriteHeadBlockHash(bc.chainDb, block.Hash()); err != nil {
- glog.Fatalf("failed to insert block number: %v", err)
- }
- // Add a new restore point if we reached some limit
- bc.checkpoint++
- if bc.checkpoint > checkpointLimit {
- if err := bc.chainDb.Put([]byte("checkpoint"), block.Hash().Bytes()); err != nil {
- glog.Fatalf("failed to create checkpoint: %v", err)
- }
- bc.checkpoint = 0
- }
- // Update the internal internal state with the head block
- bc.currentBlock = block
-}
-
-// Accessors
-func (bc *ChainManager) Genesis() *types.Block {
- return bc.genesisBlock
-}
-
-// HasHeader checks if a block header is present in the database or not, caching
-// it if present.
-func (bc *ChainManager) HasHeader(hash common.Hash) bool {
- return bc.GetHeader(hash) != nil
-}
-
-// GetHeader retrieves a block header from the database by hash, caching it if
-// found.
-func (self *ChainManager) GetHeader(hash common.Hash) *types.Header {
- // Short circuit if the header's already in the cache, retrieve otherwise
- if header, ok := self.headerCache.Get(hash); ok {
- return header.(*types.Header)
- }
- header := GetHeader(self.chainDb, hash)
- if header == nil {
- return nil
- }
- // Cache the found header for next time and return
- self.headerCache.Add(header.Hash(), header)
- return header
-}
-
-// GetHeaderByNumber retrieves a block header from the database by number,
-// caching it (associated with its hash) if found.
-func (self *ChainManager) GetHeaderByNumber(number uint64) *types.Header {
- hash := GetCanonicalHash(self.chainDb, number)
- if hash == (common.Hash{}) {
- return nil
- }
- return self.GetHeader(hash)
-}
-
-// GetBody retrieves a block body (transactions and uncles) from the database by
-// hash, caching it if found.
-func (self *ChainManager) GetBody(hash common.Hash) *types.Body {
- // Short circuit if the body's already in the cache, retrieve otherwise
- if cached, ok := self.bodyCache.Get(hash); ok {
- body := cached.(*types.Body)
- return body
- }
- body := GetBody(self.chainDb, hash)
- if body == nil {
- return nil
- }
- // Cache the found body for next time and return
- self.bodyCache.Add(hash, body)
- return body
-}
-
-// GetBodyRLP retrieves a block body in RLP encoding from the database by hash,
-// caching it if found.
-func (self *ChainManager) GetBodyRLP(hash common.Hash) rlp.RawValue {
- // Short circuit if the body's already in the cache, retrieve otherwise
- if cached, ok := self.bodyRLPCache.Get(hash); ok {
- return cached.(rlp.RawValue)
- }
- body := GetBodyRLP(self.chainDb, hash)
- if len(body) == 0 {
- return nil
- }
- // Cache the found body for next time and return
- self.bodyRLPCache.Add(hash, body)
- return body
-}
-
-// GetTd retrieves a block's total difficulty in the canonical chain from the
-// database by hash, caching it if found.
-func (self *ChainManager) GetTd(hash common.Hash) *big.Int {
- // Short circuit if the td's already in the cache, retrieve otherwise
- if cached, ok := self.tdCache.Get(hash); ok {
- return cached.(*big.Int)
- }
- td := GetTd(self.chainDb, hash)
- if td == nil {
- return nil
- }
- // Cache the found body for next time and return
- self.tdCache.Add(hash, td)
- return td
-}
-
-// HasBlock checks if a block is fully present in the database or not, caching
-// it if present.
-func (bc *ChainManager) HasBlock(hash common.Hash) bool {
- return bc.GetBlock(hash) != nil
-}
-
-// GetBlock retrieves a block from the database by hash, caching it if found.
-func (self *ChainManager) GetBlock(hash common.Hash) *types.Block {
- // Short circuit if the block's already in the cache, retrieve otherwise
- if block, ok := self.blockCache.Get(hash); ok {
- return block.(*types.Block)
- }
- block := GetBlock(self.chainDb, hash)
- if block == nil {
- return nil
- }
- // Cache the found block for next time and return
- self.blockCache.Add(block.Hash(), block)
- return block
-}
-
-// GetBlockByNumber retrieves a block from the database by number, caching it
-// (associated with its hash) if found.
-func (self *ChainManager) GetBlockByNumber(number uint64) *types.Block {
- hash := GetCanonicalHash(self.chainDb, number)
- if hash == (common.Hash{}) {
- return nil
- }
- return self.GetBlock(hash)
-}
-
-// GetBlockHashesFromHash retrieves a number of block hashes starting at a given
-// hash, fetching towards the genesis block.
-func (self *ChainManager) GetBlockHashesFromHash(hash common.Hash, max uint64) []common.Hash {
- // Get the origin header from which to fetch
- header := self.GetHeader(hash)
- if header == nil {
- return nil
- }
- // Iterate the headers until enough is collected or the genesis reached
- chain := make([]common.Hash, 0, max)
- for i := uint64(0); i < max; i++ {
- if header = self.GetHeader(header.ParentHash); header == nil {
- break
- }
- chain = append(chain, header.Hash())
- if header.Number.Cmp(common.Big0) == 0 {
- break
- }
- }
- return chain
-}
-
-// [deprecated by eth/62]
-// GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors.
-func (self *ChainManager) GetBlocksFromHash(hash common.Hash, n int) (blocks []*types.Block) {
- for i := 0; i < n; i++ {
- block := self.GetBlock(hash)
- if block == nil {
- break
- }
- blocks = append(blocks, block)
- hash = block.ParentHash()
- }
- return
-}
-
-func (self *ChainManager) GetUnclesInChain(block *types.Block, length int) (uncles []*types.Header) {
- for i := 0; block != nil && i < length; i++ {
- uncles = append(uncles, block.Uncles()...)
- block = self.GetBlock(block.ParentHash())
- }
-
- return
-}
-
-// setTotalDifficulty updates the TD of the chain manager. Note, this function
-// assumes that the `mu` mutex is held!
-func (bc *ChainManager) setTotalDifficulty(td *big.Int) {
- bc.td = new(big.Int).Set(td)
-}
-
-func (bc *ChainManager) Stop() {
- if !atomic.CompareAndSwapInt32(&bc.running, 0, 1) {
- return
- }
- close(bc.quit)
- atomic.StoreInt32(&bc.procInterrupt, 1)
-
- bc.wg.Wait()
-
- glog.V(logger.Info).Infoln("Chain manager stopped")
-}
-
-type queueEvent struct {
- queue []interface{}
- canonicalCount int
- sideCount int
- splitCount int
-}
-
-func (self *ChainManager) procFutureBlocks() {
- blocks := make([]*types.Block, self.futureBlocks.Len())
- for i, hash := range self.futureBlocks.Keys() {
- block, _ := self.futureBlocks.Get(hash)
- blocks[i] = block.(*types.Block)
- }
- if len(blocks) > 0 {
- types.BlockBy(types.Number).Sort(blocks)
- self.InsertChain(blocks)
- }
-}
-
-type writeStatus byte
-
-const (
- NonStatTy writeStatus = iota
- CanonStatTy
- SplitStatTy
- SideStatTy
-)
-
-// WriteBlock writes the block to the chain.
-func (self *ChainManager) WriteBlock(block *types.Block) (status writeStatus, err error) {
- self.wg.Add(1)
- defer self.wg.Done()
-
- // Calculate the total difficulty of the block
- ptd := self.GetTd(block.ParentHash())
- if ptd == nil {
- return NonStatTy, ParentError(block.ParentHash())
- }
- td := new(big.Int).Add(block.Difficulty(), ptd)
-
- self.mu.RLock()
- cblock := self.currentBlock
- self.mu.RUnlock()
-
- // Compare the TD of the last known block in the canonical chain to make sure it's greater.
- // At this point it's possible that a different chain (fork) becomes the new canonical chain.
- if td.Cmp(self.Td()) > 0 {
- // chain fork
- if block.ParentHash() != cblock.Hash() {
- // during split we merge two different chains and create the new canonical chain
- err := self.reorg(cblock, block)
- if err != nil {
- return NonStatTy, err
- }
- }
- status = CanonStatTy
-
- self.mu.Lock()
- self.setTotalDifficulty(td)
- self.insert(block)
- self.mu.Unlock()
- } else {
- status = SideStatTy
- }
-
- if err := WriteTd(self.chainDb, block.Hash(), td); err != nil {
- glog.Fatalf("failed to write block total difficulty: %v", err)
- }
- if err := WriteBlock(self.chainDb, block); err != nil {
- glog.Fatalf("filed to write block contents: %v", err)
- }
- // Delete from future blocks
- self.futureBlocks.Remove(block.Hash())
-
- return
-}
-
-// InsertChain will attempt to insert the given chain in to the canonical chain or, otherwise, create a fork. It an error is returned
-// it will return the index number of the failing block as well an error describing what went wrong (for possible errors see core/errors.go).
-func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) {
- self.wg.Add(1)
- defer self.wg.Done()
-
- self.chainmu.Lock()
- defer self.chainmu.Unlock()
-
- // A queued approach to delivering events. This is generally
- // faster than direct delivery and requires much less mutex
- // acquiring.
- var (
- queue = make([]interface{}, len(chain))
- queueEvent = queueEvent{queue: queue}
- stats struct{ queued, processed, ignored int }
- tstart = time.Now()
-
- nonceChecked = make([]bool, len(chain))
- )
-
- // Start the parallel nonce verifier.
- nonceAbort, nonceResults := verifyNoncesFromBlocks(self.pow, chain)
- defer close(nonceAbort)
-
- txcount := 0
- for i, block := range chain {
- if atomic.LoadInt32(&self.procInterrupt) == 1 {
- glog.V(logger.Debug).Infoln("Premature abort during chain processing")
- break
- }
-
- bstart := time.Now()
- // Wait for block i's nonce to be verified before processing
- // its state transition.
- for !nonceChecked[i] {
- r := <-nonceResults
- nonceChecked[r.index] = true
- if !r.valid {
- block := chain[r.index]
- return r.index, &BlockNonceErr{Hash: block.Hash(), Number: block.Number(), Nonce: block.Nonce()}
- }
- }
-
- if BadHashes[block.Hash()] {
- err := BadHashError(block.Hash())
- blockErr(block, err)
- return i, err
- }
- // Call in to the block processor and check for errors. It's likely that if one block fails
- // all others will fail too (unless a known block is returned).
- logs, receipts, err := self.processor.Process(block)
- if err != nil {
- if IsKnownBlockErr(err) {
- stats.ignored++
- continue
- }
-
- if err == BlockFutureErr {
- // Allow up to MaxFuture second in the future blocks. If this limit
- // is exceeded the chain is discarded and processed at a later time
- // if given.
- max := big.NewInt(time.Now().Unix() + maxTimeFutureBlocks)
- if block.Time().Cmp(max) == 1 {
- return i, fmt.Errorf("%v: BlockFutureErr, %v > %v", BlockFutureErr, block.Time(), max)
- }
-
- self.futureBlocks.Add(block.Hash(), block)
- stats.queued++
- continue
- }
-
- if IsParentErr(err) && self.futureBlocks.Contains(block.ParentHash()) {
- self.futureBlocks.Add(block.Hash(), block)
- stats.queued++
- continue
- }
-
- blockErr(block, err)
-
- go ReportBlock(block, err)
-
- return i, err
- }
- if err := PutBlockReceipts(self.chainDb, block, receipts); err != nil {
- glog.V(logger.Warn).Infoln("error writing block receipts:", err)
- }
-
- txcount += len(block.Transactions())
- // write the block to the chain and get the status
- status, err := self.WriteBlock(block)
- if err != nil {
- return i, err
- }
- switch status {
- case CanonStatTy:
- if glog.V(logger.Debug) {
- glog.Infof("[%v] inserted block #%d (%d TXs %v G %d UNCs) (%x...). Took %v\n", time.Now().UnixNano(), block.Number(), len(block.Transactions()), block.GasUsed(), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart))
- }
- queue[i] = ChainEvent{block, block.Hash(), logs}
- queueEvent.canonicalCount++
-
- // This puts transactions in a extra db for rpc
- PutTransactions(self.chainDb, block, block.Transactions())
- // store the receipts
- PutReceipts(self.chainDb, receipts)
- case SideStatTy:
- if glog.V(logger.Detail) {
- glog.Infof("inserted forked block #%d (TD=%v) (%d TXs %d UNCs) (%x...). Took %v\n", block.Number(), block.Difficulty(), len(block.Transactions()), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart))
- }
- queue[i] = ChainSideEvent{block, logs}
- queueEvent.sideCount++
- case SplitStatTy:
- queue[i] = ChainSplitEvent{block, logs}
- queueEvent.splitCount++
- }
- stats.processed++
- }
-
- if (stats.queued > 0 || stats.processed > 0 || stats.ignored > 0) && bool(glog.V(logger.Info)) {
- tend := time.Since(tstart)
- start, end := chain[0], chain[len(chain)-1]
- glog.Infof("imported %d block(s) (%d queued %d ignored) including %d txs in %v. #%v [%x / %x]\n", stats.processed, stats.queued, stats.ignored, txcount, tend, end.Number(), start.Hash().Bytes()[:4], end.Hash().Bytes()[:4])
- }
-
- go self.eventMux.Post(queueEvent)
-
- return 0, nil
-}
-
-// reorgs takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them
-// to be part of the new canonical chain and accumulates potential missing transactions and post an
-// event about them
-func (self *ChainManager) reorg(oldBlock, newBlock *types.Block) error {
- self.mu.Lock()
- defer self.mu.Unlock()
-
- var (
- newChain types.Blocks
- commonBlock *types.Block
- oldStart = oldBlock
- newStart = newBlock
- deletedTxs types.Transactions
- )
-
- // first reduce whoever is higher bound
- if oldBlock.NumberU64() > newBlock.NumberU64() {
- // reduce old chain
- for oldBlock = oldBlock; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = self.GetBlock(oldBlock.ParentHash()) {
- deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
- }
- } else {
- // reduce new chain and append new chain blocks for inserting later on
- for newBlock = newBlock; newBlock != nil && newBlock.NumberU64() != oldBlock.NumberU64(); newBlock = self.GetBlock(newBlock.ParentHash()) {
- newChain = append(newChain, newBlock)
- }
- }
- if oldBlock == nil {
- return fmt.Errorf("Invalid old chain")
- }
- if newBlock == nil {
- return fmt.Errorf("Invalid new chain")
- }
-
- numSplit := newBlock.Number()
- for {
- if oldBlock.Hash() == newBlock.Hash() {
- commonBlock = oldBlock
- break
- }
- newChain = append(newChain, newBlock)
- deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
-
- oldBlock, newBlock = self.GetBlock(oldBlock.ParentHash()), self.GetBlock(newBlock.ParentHash())
- if oldBlock == nil {
- return fmt.Errorf("Invalid old chain")
- }
- if newBlock == nil {
- return fmt.Errorf("Invalid new chain")
- }
- }
-
- if glog.V(logger.Debug) {
- commonHash := commonBlock.Hash()
- glog.Infof("Chain split detected @ %x. Reorganising chain from #%v %x to %x", commonHash[:4], numSplit, oldStart.Hash().Bytes()[:4], newStart.Hash().Bytes()[:4])
- }
-
- var addedTxs types.Transactions
- // insert blocks. Order does not matter. Last block will be written in ImportChain itself which creates the new head properly
- for _, block := range newChain {
- // insert the block in the canonical way, re-writing history
- self.insert(block)
- // write canonical receipts and transactions
- PutTransactions(self.chainDb, block, block.Transactions())
- PutReceipts(self.chainDb, GetBlockReceipts(self.chainDb, block.Hash()))
-
- addedTxs = append(addedTxs, block.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
- for _, tx := range diff {
- DeleteReceipt(self.chainDb, tx.Hash())
- DeleteTransaction(self.chainDb, tx.Hash())
- }
- self.eventMux.Post(RemovedTransactionEvent{diff})
-
- return nil
-}
-
-func (self *ChainManager) update() {
- events := self.eventMux.Subscribe(queueEvent{})
- futureTimer := time.Tick(5 * time.Second)
-out:
- for {
- select {
- case ev := <-events.Chan():
- switch ev := ev.(type) {
- case queueEvent:
- for _, event := range ev.queue {
- switch event := event.(type) {
- case ChainEvent:
- // We need some control over the mining operation. Acquiring locks and waiting for the miner to create new block takes too long
- // and in most cases isn't even necessary.
- if self.currentBlock.Hash() == event.Hash {
- self.currentGasLimit = CalcGasLimit(event.Block)
- self.eventMux.Post(ChainHeadEvent{event.Block})
- }
- }
- self.eventMux.Post(event)
- }
- }
- case <-futureTimer:
- self.procFutureBlocks()
- case <-self.quit:
- break out
- }
- }
-}
-
-func blockErr(block *types.Block, err error) {
- h := block.Header()
- glog.V(logger.Error).Infof("Bad block #%v (%x)\n", h.Number, h.Hash().Bytes())
- glog.V(logger.Error).Infoln(err)
- glog.V(logger.Debug).Infoln(verifyNonces)
-}
diff --git a/core/chain_manager_test.go b/core/chain_manager_test.go
deleted file mode 100644
index 6cfafb8c0..000000000
--- a/core/chain_manager_test.go
+++ /dev/null
@@ -1,652 +0,0 @@
-// Copyright 2014 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 (
- "fmt"
- "math/big"
- "math/rand"
- "os"
- "path/filepath"
- "runtime"
- "strconv"
- "testing"
-
- "github.com/ethereum/ethash"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/pow"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/hashicorp/golang-lru"
-)
-
-func init() {
- runtime.GOMAXPROCS(runtime.NumCPU())
-}
-
-func thePow() pow.PoW {
- pow, _ := ethash.NewForTesting()
- return pow
-}
-
-func theChainManager(db ethdb.Database, t *testing.T) *ChainManager {
- var eventMux event.TypeMux
- WriteTestNetGenesisBlock(db, 0)
- chainMan, err := NewChainManager(db, thePow(), &eventMux)
- if err != nil {
- t.Error("failed creating chainmanager:", err)
- t.FailNow()
- return nil
- }
- blockMan := NewBlockProcessor(db, nil, chainMan, &eventMux)
- chainMan.SetProcessor(blockMan)
-
- return chainMan
-}
-
-// Test fork of length N starting from block i
-func testFork(t *testing.T, bman *BlockProcessor, i, N int, f func(td1, td2 *big.Int)) {
- // switch databases to process the new chain
- db, err := ethdb.NewMemDatabase()
- if err != nil {
- t.Fatal("Failed to create db:", err)
- }
- // copy old chain up to i into new db with deterministic canonical
- bman2, err := newCanonical(i, db)
- if err != nil {
- t.Fatal("could not make new canonical in testFork", err)
- }
- // assert the bmans have the same block at i
- bi1 := bman.bc.GetBlockByNumber(uint64(i)).Hash()
- bi2 := bman2.bc.GetBlockByNumber(uint64(i)).Hash()
- if bi1 != bi2 {
- fmt.Printf("%+v\n%+v\n\n", bi1, bi2)
- t.Fatal("chains do not have the same hash at height", i)
- }
- bman2.bc.SetProcessor(bman2)
-
- // extend the fork
- parent := bman2.bc.CurrentBlock()
- chainB := makeChain(parent, N, db, forkSeed)
- _, err = bman2.bc.InsertChain(chainB)
- if err != nil {
- t.Fatal("Insert chain error for fork:", err)
- }
-
- tdpre := bman.bc.Td()
- // Test the fork's blocks on the original chain
- td, err := testChain(chainB, bman)
- if err != nil {
- t.Fatal("expected chainB not to give errors:", err)
- }
- // Compare difficulties
- f(tdpre, td)
-
- // Loop over parents making sure reconstruction is done properly
-}
-
-func printChain(bc *ChainManager) {
- for i := bc.CurrentBlock().Number().Uint64(); i > 0; i-- {
- b := bc.GetBlockByNumber(uint64(i))
- fmt.Printf("\t%x %v\n", b.Hash(), b.Difficulty())
- }
-}
-
-// process blocks against a chain
-func testChain(chainB types.Blocks, bman *BlockProcessor) (*big.Int, error) {
- for _, block := range chainB {
- _, _, err := bman.bc.processor.Process(block)
- if err != nil {
- if IsKnownBlockErr(err) {
- continue
- }
- return nil, err
- }
- bman.bc.mu.Lock()
- WriteTd(bman.bc.chainDb, block.Hash(), new(big.Int).Add(block.Difficulty(), bman.bc.GetTd(block.ParentHash())))
- WriteBlock(bman.bc.chainDb, block)
- bman.bc.mu.Unlock()
- }
- return bman.bc.GetTd(chainB[len(chainB)-1].Hash()), nil
-}
-
-func loadChain(fn string, t *testing.T) (types.Blocks, error) {
- fh, err := os.OpenFile(filepath.Join("..", "_data", fn), os.O_RDONLY, os.ModePerm)
- if err != nil {
- return nil, err
- }
- defer fh.Close()
-
- var chain types.Blocks
- if err := rlp.Decode(fh, &chain); err != nil {
- return nil, err
- }
-
- return chain, nil
-}
-
-func insertChain(done chan bool, chainMan *ChainManager, chain types.Blocks, t *testing.T) {
- _, err := chainMan.InsertChain(chain)
- if err != nil {
- fmt.Println(err)
- t.FailNow()
- }
- done <- true
-}
-
-func TestExtendCanonical(t *testing.T) {
- CanonicalLength := 5
- db, err := ethdb.NewMemDatabase()
- if err != nil {
- t.Fatal("Failed to create db:", err)
- }
- // make first chain starting from genesis
- bman, err := newCanonical(CanonicalLength, db)
- if err != nil {
- t.Fatal("Could not make new canonical chain:", err)
- }
- f := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) <= 0 {
- t.Error("expected chainB to have higher difficulty. Got", td2, "expected more than", td1)
- }
- }
- // Start fork from current height (CanonicalLength)
- testFork(t, bman, CanonicalLength, 1, f)
- testFork(t, bman, CanonicalLength, 2, f)
- testFork(t, bman, CanonicalLength, 5, f)
- testFork(t, bman, CanonicalLength, 10, f)
-}
-
-func TestShorterFork(t *testing.T) {
- db, err := ethdb.NewMemDatabase()
- if err != nil {
- t.Fatal("Failed to create db:", err)
- }
- // make first chain starting from genesis
- bman, err := newCanonical(10, db)
- if err != nil {
- t.Fatal("Could not make new canonical chain:", err)
- }
- f := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) >= 0 {
- t.Error("expected chainB to have lower difficulty. Got", td2, "expected less than", td1)
- }
- }
- // Sum of numbers must be less than 10
- // for this to be a shorter fork
- testFork(t, bman, 0, 3, f)
- testFork(t, bman, 0, 7, f)
- testFork(t, bman, 1, 1, f)
- testFork(t, bman, 1, 7, f)
- testFork(t, bman, 5, 3, f)
- testFork(t, bman, 5, 4, f)
-}
-
-func TestLongerFork(t *testing.T) {
- db, err := ethdb.NewMemDatabase()
- if err != nil {
- t.Fatal("Failed to create db:", err)
- }
- // make first chain starting from genesis
- bman, err := newCanonical(10, db)
- if err != nil {
- t.Fatal("Could not make new canonical chain:", err)
- }
- f := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) <= 0 {
- t.Error("expected chainB to have higher difficulty. Got", td2, "expected more than", td1)
- }
- }
- // Sum of numbers must be greater than 10
- // for this to be a longer fork
- testFork(t, bman, 0, 11, f)
- testFork(t, bman, 0, 15, f)
- testFork(t, bman, 1, 10, f)
- testFork(t, bman, 1, 12, f)
- testFork(t, bman, 5, 6, f)
- testFork(t, bman, 5, 8, f)
-}
-
-func TestEqualFork(t *testing.T) {
- db, err := ethdb.NewMemDatabase()
- if err != nil {
- t.Fatal("Failed to create db:", err)
- }
- bman, err := newCanonical(10, db)
- if err != nil {
- t.Fatal("Could not make new canonical chain:", err)
- }
- f := func(td1, td2 *big.Int) {
- if td2.Cmp(td1) != 0 {
- t.Error("expected chainB to have equal difficulty. Got", td2, "expected ", td1)
- }
- }
- // Sum of numbers must be equal to 10
- // for this to be an equal fork
- testFork(t, bman, 0, 10, f)
- testFork(t, bman, 1, 9, f)
- testFork(t, bman, 2, 8, f)
- testFork(t, bman, 5, 5, f)
- testFork(t, bman, 6, 4, f)
- testFork(t, bman, 9, 1, f)
-}
-
-func TestBrokenChain(t *testing.T) {
- db, err := ethdb.NewMemDatabase()
- if err != nil {
- t.Fatal("Failed to create db:", err)
- }
- bman, err := newCanonical(10, db)
- if err != nil {
- t.Fatal("Could not make new canonical chain:", err)
- }
- db2, err := ethdb.NewMemDatabase()
- if err != nil {
- t.Fatal("Failed to create db:", err)
- }
- bman2, err := newCanonical(10, db2)
- if err != nil {
- t.Fatal("Could not make new canonical chain:", err)
- }
- bman2.bc.SetProcessor(bman2)
- parent := bman2.bc.CurrentBlock()
- chainB := makeChain(parent, 5, db2, forkSeed)
- chainB = chainB[1:]
- _, err = testChain(chainB, bman)
- if err == nil {
- t.Error("expected broken chain to return error")
- }
-}
-
-func TestChainInsertions(t *testing.T) {
- t.Skip("Skipped: outdated test files")
-
- db, _ := ethdb.NewMemDatabase()
-
- chain1, err := loadChain("valid1", t)
- if err != nil {
- fmt.Println(err)
- t.FailNow()
- }
-
- chain2, err := loadChain("valid2", t)
- if err != nil {
- fmt.Println(err)
- t.FailNow()
- }
-
- chainMan := theChainManager(db, t)
-
- const max = 2
- done := make(chan bool, max)
-
- go insertChain(done, chainMan, chain1, t)
- go insertChain(done, chainMan, chain2, t)
-
- for i := 0; i < max; i++ {
- <-done
- }
-
- if chain2[len(chain2)-1].Hash() != chainMan.CurrentBlock().Hash() {
- t.Error("chain2 is canonical and shouldn't be")
- }
-
- if chain1[len(chain1)-1].Hash() != chainMan.CurrentBlock().Hash() {
- t.Error("chain1 isn't canonical and should be")
- }
-}
-
-func TestChainMultipleInsertions(t *testing.T) {
- t.Skip("Skipped: outdated test files")
-
- db, _ := ethdb.NewMemDatabase()
-
- const max = 4
- chains := make([]types.Blocks, max)
- var longest int
- for i := 0; i < max; i++ {
- var err error
- name := "valid" + strconv.Itoa(i+1)
- chains[i], err = loadChain(name, t)
- if len(chains[i]) >= len(chains[longest]) {
- longest = i
- }
- fmt.Println("loaded", name, "with a length of", len(chains[i]))
- if err != nil {
- fmt.Println(err)
- t.FailNow()
- }
- }
-
- chainMan := theChainManager(db, t)
-
- done := make(chan bool, max)
- for i, chain := range chains {
- // XXX the go routine would otherwise reference the same (chain[3]) variable and fail
- i := i
- chain := chain
- go func() {
- insertChain(done, chainMan, chain, t)
- fmt.Println(i, "done")
- }()
- }
-
- for i := 0; i < max; i++ {
- <-done
- }
-
- if chains[longest][len(chains[longest])-1].Hash() != chainMan.CurrentBlock().Hash() {
- t.Error("Invalid canonical chain")
- }
-}
-
-type bproc struct{}
-
-func (bproc) Process(*types.Block) (state.Logs, types.Receipts, error) { return nil, nil, nil }
-
-func makeChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block {
- var chain []*types.Block
- for i, difficulty := range d {
- header := &types.Header{
- Coinbase: common.Address{seed},
- Number: big.NewInt(int64(i + 1)),
- Difficulty: big.NewInt(int64(difficulty)),
- }
- if i == 0 {
- header.ParentHash = genesis.Hash()
- } else {
- header.ParentHash = chain[i-1].Hash()
- }
- block := types.NewBlockWithHeader(header)
- chain = append(chain, block)
- }
- return chain
-}
-
-func chm(genesis *types.Block, db ethdb.Database) *ChainManager {
- var eventMux event.TypeMux
- bc := &ChainManager{chainDb: db, genesisBlock: genesis, eventMux: &eventMux, pow: FakePow{}}
- bc.headerCache, _ = lru.New(100)
- bc.bodyCache, _ = lru.New(100)
- bc.bodyRLPCache, _ = lru.New(100)
- bc.tdCache, _ = lru.New(100)
- bc.blockCache, _ = lru.New(100)
- bc.futureBlocks, _ = lru.New(100)
- bc.processor = bproc{}
- bc.ResetWithGenesisBlock(genesis)
-
- return bc
-}
-
-func TestReorgLongest(t *testing.T) {
- db, _ := ethdb.NewMemDatabase()
-
- genesis, err := WriteTestNetGenesisBlock(db, 0)
- if err != nil {
- t.Error(err)
- t.FailNow()
- }
- bc := chm(genesis, db)
-
- chain1 := makeChainWithDiff(genesis, []int{1, 2, 4}, 10)
- chain2 := makeChainWithDiff(genesis, []int{1, 2, 3, 4}, 11)
-
- bc.InsertChain(chain1)
- bc.InsertChain(chain2)
-
- prev := bc.CurrentBlock()
- for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) {
- if prev.ParentHash() != block.Hash() {
- t.Errorf("parent hash mismatch %x - %x", prev.ParentHash(), block.Hash())
- }
- }
-}
-
-func TestBadHashes(t *testing.T) {
- db, _ := ethdb.NewMemDatabase()
- genesis, err := WriteTestNetGenesisBlock(db, 0)
- if err != nil {
- t.Error(err)
- t.FailNow()
- }
- bc := chm(genesis, db)
-
- chain := makeChainWithDiff(genesis, []int{1, 2, 4}, 10)
- BadHashes[chain[2].Header().Hash()] = true
-
- _, err = bc.InsertChain(chain)
- if !IsBadHashError(err) {
- t.Errorf("error mismatch: want: BadHashError, have: %v", err)
- }
-}
-
-func TestReorgBadHashes(t *testing.T) {
- db, _ := ethdb.NewMemDatabase()
- genesis, err := WriteTestNetGenesisBlock(db, 0)
- if err != nil {
- t.Error(err)
- t.FailNow()
- }
- bc := chm(genesis, db)
-
- chain := makeChainWithDiff(genesis, []int{1, 2, 3, 4}, 11)
- bc.InsertChain(chain)
-
- if chain[3].Header().Hash() != bc.LastBlockHash() {
- t.Errorf("last block hash mismatch: want: %x, have: %x", chain[3].Header().Hash(), bc.LastBlockHash())
- }
-
- // NewChainManager should check BadHashes when loading it db
- BadHashes[chain[3].Header().Hash()] = true
-
- var eventMux event.TypeMux
- ncm, err := NewChainManager(db, FakePow{}, &eventMux)
- if err != nil {
- t.Errorf("NewChainManager err: %s", err)
- }
-
- // check it set head to (valid) parent of bad hash block
- if chain[2].Header().Hash() != ncm.LastBlockHash() {
- t.Errorf("last block hash mismatch: want: %x, have: %x", chain[2].Header().Hash(), ncm.LastBlockHash())
- }
-
- if chain[2].Header().GasLimit.Cmp(ncm.GasLimit()) != 0 {
- t.Errorf("current block gasLimit mismatch: want: %x, have: %x", chain[2].Header().GasLimit, ncm.GasLimit())
- }
-}
-
-func TestReorgShortest(t *testing.T) {
- db, _ := ethdb.NewMemDatabase()
- genesis, err := WriteTestNetGenesisBlock(db, 0)
- if err != nil {
- t.Error(err)
- t.FailNow()
- }
- bc := chm(genesis, db)
-
- chain1 := makeChainWithDiff(genesis, []int{1, 2, 3, 4}, 10)
- chain2 := makeChainWithDiff(genesis, []int{1, 10}, 11)
-
- bc.InsertChain(chain1)
- bc.InsertChain(chain2)
-
- prev := bc.CurrentBlock()
- for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) {
- if prev.ParentHash() != block.Hash() {
- t.Errorf("parent hash mismatch %x - %x", prev.ParentHash(), block.Hash())
- }
- }
-}
-
-func TestInsertNonceError(t *testing.T) {
- for i := 1; i < 25 && !t.Failed(); i++ {
- db, _ := ethdb.NewMemDatabase()
- genesis, err := WriteTestNetGenesisBlock(db, 0)
- if err != nil {
- t.Error(err)
- t.FailNow()
- }
- bc := chm(genesis, db)
- bc.processor = NewBlockProcessor(db, bc.pow, bc, bc.eventMux)
- blocks := makeChain(bc.currentBlock, i, db, 0)
-
- fail := rand.Int() % len(blocks)
- failblock := blocks[fail]
- bc.pow = failPow{failblock.NumberU64()}
- n, err := bc.InsertChain(blocks)
-
- // Check that the returned error indicates the nonce failure.
- if n != fail {
- t.Errorf("(i=%d) wrong failed block index: got %d, want %d", i, n, fail)
- }
- if !IsBlockNonceErr(err) {
- t.Fatalf("(i=%d) got %q, want a nonce error", i, err)
- }
- nerr := err.(*BlockNonceErr)
- if nerr.Number.Cmp(failblock.Number()) != 0 {
- t.Errorf("(i=%d) wrong block number in error, got %v, want %v", i, nerr.Number, failblock.Number())
- }
- if nerr.Hash != failblock.Hash() {
- t.Errorf("(i=%d) wrong block hash in error, got %v, want %v", i, nerr.Hash, failblock.Hash())
- }
-
- // Check that all no blocks after the failing block have been inserted.
- for _, block := range blocks[fail:] {
- if bc.HasBlock(block.Hash()) {
- t.Errorf("(i=%d) invalid block %d present in chain", i, block.NumberU64())
- }
- }
- }
-}
-
-// Tests that chain reorganizations handle transaction removals and reinsertions.
-func TestChainTxReorgs(t *testing.T) {
- params.MinGasLimit = big.NewInt(125000) // Minimum the gas limit may ever be.
- params.GenesisGasLimit = big.NewInt(3141592) // Gas limit of the Genesis block.
-
- var (
- key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
- key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
- key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
- addr1 = crypto.PubkeyToAddress(key1.PublicKey)
- addr2 = crypto.PubkeyToAddress(key2.PublicKey)
- addr3 = crypto.PubkeyToAddress(key3.PublicKey)
- db, _ = ethdb.NewMemDatabase()
- )
- genesis := WriteGenesisBlockForTesting(db,
- GenesisAccount{addr1, big.NewInt(1000000)},
- GenesisAccount{addr2, big.NewInt(1000000)},
- GenesisAccount{addr3, big.NewInt(1000000)},
- )
- // Create two transactions shared between the chains:
- // - postponed: transaction included at a later block in the forked chain
- // - swapped: transaction included at the same block number in the forked chain
- postponed, _ := types.NewTransaction(0, addr1, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1)
- swapped, _ := types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1)
-
- // Create two transactions that will be dropped by the forked chain:
- // - pastDrop: transaction dropped retroactively from a past block
- // - freshDrop: transaction dropped exactly at the block where the reorg is detected
- var pastDrop, freshDrop *types.Transaction
-
- // Create three transactions that will be added in the forked chain:
- // - pastAdd: transaction added before the reorganiztion is detected
- // - freshAdd: transaction added at the exact block the reorg is detected
- // - futureAdd: transaction added after the reorg has already finished
- var pastAdd, freshAdd, futureAdd *types.Transaction
-
- chain := GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) {
- switch i {
- case 0:
- pastDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2)
-
- gen.AddTx(pastDrop) // This transaction will be dropped in the fork from below the split point
- gen.AddTx(postponed) // This transaction will be postponed till block #3 in the fork
-
- case 2:
- freshDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2)
-
- gen.AddTx(freshDrop) // This transaction will be dropped in the fork from exactly at the split point
- gen.AddTx(swapped) // This transaction will be swapped out at the exact height
-
- gen.OffsetTime(9) // Lower the block difficulty to simulate a weaker chain
- }
- })
- // Import the chain. This runs all block validation rules.
- evmux := &event.TypeMux{}
- chainman, _ := NewChainManager(db, FakePow{}, evmux)
- chainman.SetProcessor(NewBlockProcessor(db, FakePow{}, chainman, evmux))
- if i, err := chainman.InsertChain(chain); err != nil {
- t.Fatalf("failed to insert original chain[%d]: %v", i, err)
- }
-
- // overwrite the old chain
- chain = GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) {
- switch i {
- case 0:
- pastAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
- gen.AddTx(pastAdd) // This transaction needs to be injected during reorg
-
- case 2:
- gen.AddTx(postponed) // This transaction was postponed from block #1 in the original chain
- gen.AddTx(swapped) // This transaction was swapped from the exact current spot in the original chain
-
- freshAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
- gen.AddTx(freshAdd) // This transaction will be added exactly at reorg time
-
- case 3:
- futureAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3)
- gen.AddTx(futureAdd) // This transaction will be added after a full reorg
- }
- })
- if _, err := chainman.InsertChain(chain); err != nil {
- t.Fatalf("failed to insert forked chain: %v", err)
- }
-
- // removed tx
- for i, tx := range (types.Transactions{pastDrop, freshDrop}) {
- if GetTransaction(db, tx.Hash()) != nil {
- t.Errorf("drop %d: tx found while shouldn't have been", i)
- }
- if GetReceipt(db, tx.Hash()) != nil {
- t.Errorf("drop %d: receipt found while shouldn't have been", i)
- }
- }
- // added tx
- for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) {
- if GetTransaction(db, tx.Hash()) == nil {
- t.Errorf("add %d: expected tx to be found", i)
- }
- if GetReceipt(db, tx.Hash()) == nil {
- t.Errorf("add %d: expected receipt to be found", i)
- }
- }
- // shared tx
- for i, tx := range (types.Transactions{postponed, swapped}) {
- if GetTransaction(db, tx.Hash()) == nil {
- t.Errorf("share %d: expected tx to be found", i)
- }
- if GetReceipt(db, tx.Hash()) == nil {
- t.Errorf("share %d: expected receipt to be found", i)
- }
- }
-}
diff --git a/core/chain_pow_test.go b/core/chain_pow_test.go
index 80c6a1cc0..d2b0bd144 100644
--- a/core/chain_pow_test.go
+++ b/core/chain_pow_test.go
@@ -34,7 +34,7 @@ type failPow struct {
failing uint64
}
-func (pow failPow) Search(pow.Block, <-chan struct{}) (uint64, []byte) {
+func (pow failPow) Search(pow.Block, <-chan struct{}, int) (uint64, []byte) {
return 0, nil
}
func (pow failPow) Verify(block pow.Block) bool { return block.NumberU64() != pow.failing }
@@ -47,7 +47,7 @@ type delayedPow struct {
delay time.Duration
}
-func (pow delayedPow) Search(pow.Block, <-chan struct{}) (uint64, []byte) {
+func (pow delayedPow) Search(pow.Block, <-chan struct{}, int) (uint64, []byte) {
return 0, nil
}
func (pow delayedPow) Verify(block pow.Block) bool { time.Sleep(pow.delay); return true }
@@ -60,7 +60,7 @@ func TestPowVerification(t *testing.T) {
var (
testdb, _ = ethdb.NewMemDatabase()
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
- blocks = GenerateChain(genesis, testdb, 8, nil)
+ blocks, _ = GenerateChain(genesis, testdb, 8, nil)
)
headers := make([]*types.Header, len(blocks))
for i, block := range blocks {
@@ -115,7 +115,7 @@ func testPowConcurrentVerification(t *testing.T, threads int) {
var (
testdb, _ = ethdb.NewMemDatabase()
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
- blocks = GenerateChain(genesis, testdb, 8, nil)
+ blocks, _ = GenerateChain(genesis, testdb, 8, nil)
)
headers := make([]*types.Header, len(blocks))
for i, block := range blocks {
@@ -186,7 +186,7 @@ func testPowConcurrentAbortion(t *testing.T, threads int) {
var (
testdb, _ = ethdb.NewMemDatabase()
genesis = GenesisBlockForTesting(testdb, common.Address{}, new(big.Int))
- blocks = GenerateChain(genesis, testdb, 1024, nil)
+ blocks, _ = GenerateChain(genesis, testdb, 1024, nil)
)
headers := make([]*types.Header, len(blocks))
for i, block := range blocks {
diff --git a/core/chain_util.go b/core/chain_util.go
index 33d94cebd..ddff381a1 100644
--- a/core/chain_util.go
+++ b/core/chain_util.go
@@ -18,6 +18,8 @@ package core
import (
"bytes"
+ "encoding/binary"
+ "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
@@ -32,6 +34,7 @@ import (
var (
headHeaderKey = []byte("LastHeader")
headBlockKey = []byte("LastBlock")
+ headFastKey = []byte("LastFast")
blockPrefix = []byte("block-")
blockNumPrefix = []byte("block-num-")
@@ -42,6 +45,9 @@ var (
ExpDiffPeriod = big.NewInt(100000)
blockHashPre = []byte("block-hash-") // [deprecated by eth/63]
+
+ mipmapPre = []byte("mipmap-log-bloom-")
+ MIPMapLevels = []uint64{1000000, 500000, 100000, 50000, 1000}
)
// CalcDifficulty is the difficulty adjustment algorithm. It returns
@@ -124,7 +130,7 @@ func GetCanonicalHash(db ethdb.Database, number uint64) common.Hash {
// 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
-// fast synchronization mechanism.
+// light synchronization mechanism.
func GetHeadHeaderHash(db ethdb.Database) common.Hash {
data, _ := db.Get(headHeaderKey)
if len(data) == 0 {
@@ -142,6 +148,18 @@ func GetHeadBlockHash(db ethdb.Database) 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 ethdb.Database) common.Hash {
+ data, _ := db.Get(headFastKey)
+ if len(data) == 0 {
+ return common.Hash{}
+ }
+ return common.BytesToHash(data)
+}
+
// GetHeaderRLP retrieves a block header in its raw RLP database encoding, or nil
// if the header's not found.
func GetHeaderRLP(db ethdb.Database, hash common.Hash) rlp.RawValue {
@@ -244,6 +262,15 @@ func WriteHeadBlockHash(db ethdb.Database, hash common.Hash) error {
return nil
}
+// WriteHeadFastBlockHash stores the fast head block's hash.
+func WriteHeadFastBlockHash(db ethdb.Database, hash common.Hash) error {
+ if err := db.Put(headFastKey, hash.Bytes()); err != nil {
+ glog.Fatalf("failed to store last fast block's hash into database: %v", err)
+ return err
+ }
+ return nil
+}
+
// WriteHeader serializes a block header into the database.
func WriteHeader(db ethdb.Database, header *types.Header) error {
data, err := rlp.EncodeToBytes(header)
@@ -346,3 +373,42 @@ func GetBlockByHashOld(db ethdb.Database, hash common.Hash) *types.Block {
}
return (*types.Block)(&block)
}
+
+// returns a formatted MIP mapped key by adding prefix, canonical number and level
+//
+// ex. fn(98, 1000) = (prefix || 1000 || 0)
+func mipmapKey(num, level uint64) []byte {
+ lkey := make([]byte, 8)
+ binary.BigEndian.PutUint64(lkey, level)
+ key := new(big.Int).SetUint64(num / level * level)
+
+ return append(mipmapPre, append(lkey, key.Bytes()...)...)
+}
+
+// WriteMapmapBloom writes each address included in the receipts' logs to the
+// MIP bloom bin.
+func WriteMipmapBloom(db ethdb.Database, number uint64, receipts types.Receipts) error {
+ batch := db.NewBatch()
+ for _, level := range MIPMapLevels {
+ key := mipmapKey(number, level)
+ bloomDat, _ := db.Get(key)
+ bloom := types.BytesToBloom(bloomDat)
+ for _, receipt := range receipts {
+ for _, log := range receipt.Logs {
+ bloom.Add(log.Address.Big())
+ }
+ }
+ batch.Put(key, bloom.Bytes())
+ }
+ if err := batch.Write(); err != nil {
+ return fmt.Errorf("mipmap write fail for: %d: %v", number, err)
+ }
+ return nil
+}
+
+// GetMipmapBloom returns a bloom filter using the number and level as input
+// parameters. For available levels see MIPMapLevels.
+func GetMipmapBloom(db ethdb.Database, number, level uint64) types.Bloom {
+ bloomDat, _ := db.Get(mipmapKey(number, level))
+ return types.BytesToBloom(bloomDat)
+}
diff --git a/core/chain_util_test.go b/core/chain_util_test.go
index 3f0446715..0bbcbbe53 100644
--- a/core/chain_util_test.go
+++ b/core/chain_util_test.go
@@ -18,12 +18,15 @@ package core
import (
"encoding/json"
+ "io/ioutil"
"math/big"
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
@@ -160,7 +163,12 @@ func TestBlockStorage(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
// Create a test block to move around the database and make sure it's really new
- block := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block")})
+ block := types.NewBlockWithHeader(&types.Header{
+ Extra: []byte("test block"),
+ UncleHash: types.EmptyUncleHash,
+ TxHash: types.EmptyRootHash,
+ ReceiptHash: types.EmptyRootHash,
+ })
if entry := GetBlock(db, block.Hash()); entry != nil {
t.Fatalf("Non existent block returned: %v", entry)
}
@@ -205,8 +213,12 @@ func TestBlockStorage(t *testing.T) {
// Tests that partial block contents don't get reassembled into full blocks.
func TestPartialBlockStorage(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
- block := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block")})
-
+ block := types.NewBlockWithHeader(&types.Header{
+ Extra: []byte("test block"),
+ UncleHash: types.EmptyUncleHash,
+ TxHash: types.EmptyRootHash,
+ 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)
@@ -295,6 +307,7 @@ func TestHeadStorage(t *testing.T) {
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{}) {
@@ -303,6 +316,9 @@ func TestHeadStorage(t *testing.T) {
if entry := GetHeadBlockHash(db); entry != (common.Hash{}) {
t.Fatalf("Non head block entry returned: %v", entry)
}
+ if entry := GetHeadFastBlockHash(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)
@@ -310,6 +326,9 @@ func TestHeadStorage(t *testing.T) {
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)
+ }
// Check that both heads are present, and different (i.e. two heads maintained)
if entry := GetHeadHeaderHash(db); entry != blockHead.Hash() {
t.Fatalf("Head header hash mismatch: have %v, want %v", entry, blockHead.Hash())
@@ -317,4 +336,116 @@ func TestHeadStorage(t *testing.T) {
if entry := GetHeadBlockHash(db); entry != blockFull.Hash() {
t.Fatalf("Head block hash mismatch: have %v, want %v", entry, blockFull.Hash())
}
+ if entry := GetHeadFastBlockHash(db); entry != blockFast.Hash() {
+ t.Fatalf("Fast head block hash mismatch: have %v, want %v", entry, blockFast.Hash())
+ }
+}
+
+func TestMipmapBloom(t *testing.T) {
+ db, _ := ethdb.NewMemDatabase()
+
+ receipt1 := new(types.Receipt)
+ receipt1.Logs = vm.Logs{
+ &vm.Log{Address: common.BytesToAddress([]byte("test"))},
+ &vm.Log{Address: common.BytesToAddress([]byte("address"))},
+ }
+ receipt2 := new(types.Receipt)
+ receipt2.Logs = vm.Logs{
+ &vm.Log{Address: common.BytesToAddress([]byte("test"))},
+ &vm.Log{Address: common.BytesToAddress([]byte("address1"))},
+ }
+
+ WriteMipmapBloom(db, 1, types.Receipts{receipt1})
+ WriteMipmapBloom(db, 2, types.Receipts{receipt2})
+
+ for _, level := range MIPMapLevels {
+ bloom := GetMipmapBloom(db, 2, level)
+ if !bloom.Test(new(big.Int).SetBytes([]byte("address1"))) {
+ t.Error("expected test to be included on level:", level)
+ }
+ }
+
+ // reset
+ db, _ = ethdb.NewMemDatabase()
+ receipt := new(types.Receipt)
+ receipt.Logs = vm.Logs{
+ &vm.Log{Address: common.BytesToAddress([]byte("test"))},
+ }
+ WriteMipmapBloom(db, 999, types.Receipts{receipt1})
+
+ receipt = new(types.Receipt)
+ receipt.Logs = vm.Logs{
+ &vm.Log{Address: common.BytesToAddress([]byte("test 1"))},
+ }
+ WriteMipmapBloom(db, 1000, types.Receipts{receipt})
+
+ bloom := GetMipmapBloom(db, 1000, 1000)
+ if bloom.TestBytes([]byte("test")) {
+ t.Error("test should not have been included")
+ }
+}
+
+func TestMipmapChain(t *testing.T) {
+ dir, err := ioutil.TempDir("", "mipmap")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ var (
+ db, _ = ethdb.NewLDBDatabase(dir, 16)
+ key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ addr = crypto.PubkeyToAddress(key1.PublicKey)
+ addr2 = common.BytesToAddress([]byte("jeff"))
+
+ hash1 = common.BytesToHash([]byte("topic1"))
+ )
+ defer db.Close()
+
+ genesis := WriteGenesisBlockForTesting(db, GenesisAccount{addr, big.NewInt(1000000)})
+ chain, receipts := GenerateChain(genesis, db, 1010, func(i int, gen *BlockGen) {
+ var receipts types.Receipts
+ switch i {
+ case 1:
+ receipt := types.NewReceipt(nil, new(big.Int))
+ receipt.Logs = vm.Logs{
+ &vm.Log{
+ Address: addr,
+ Topics: []common.Hash{hash1},
+ },
+ }
+ gen.AddUncheckedReceipt(receipt)
+ receipts = types.Receipts{receipt}
+ case 1000:
+ receipt := types.NewReceipt(nil, new(big.Int))
+ receipt.Logs = vm.Logs{&vm.Log{Address: addr2}}
+ gen.AddUncheckedReceipt(receipt)
+ receipts = types.Receipts{receipt}
+
+ }
+
+ // store the receipts
+ err := PutReceipts(db, receipts)
+ if err != nil {
+ t.Fatal(err)
+ }
+ WriteMipmapBloom(db, uint64(i+1), receipts)
+ })
+ for i, block := range chain {
+ WriteBlock(db, block)
+ if err := WriteCanonicalHash(db, block.Hash(), block.NumberU64()); err != nil {
+ t.Fatalf("failed to insert block number: %v", err)
+ }
+ if err := WriteHeadBlockHash(db, block.Hash()); err != nil {
+ t.Fatalf("failed to insert block number: %v", err)
+ }
+ if err := PutBlockReceipts(db, block.Hash(), receipts[i]); err != nil {
+ t.Fatal("error writing block receipts:", err)
+ }
+ }
+
+ bloom := GetMipmapBloom(db, 0, 1000)
+ if bloom.TestBytes(addr2[:]) {
+ t.Error("address was included in bloom and should not have")
+ }
}
diff --git a/core/error.go b/core/error.go
index 5e32124a7..0ba506f46 100644
--- a/core/error.go
+++ b/core/error.go
@@ -111,7 +111,7 @@ type BlockNonceErr struct {
}
func (err *BlockNonceErr) Error() string {
- return fmt.Sprintf("block %d (%v) nonce is invalid (got %d)", err.Number, err.Hash, err.Nonce)
+ return fmt.Sprintf("nonce for #%d [%x…] is invalid (got %d)", err.Number, err.Hash, err.Nonce)
}
// IsBlockNonceErr returns true for invalid block nonce errors.
@@ -188,3 +188,16 @@ func IsBadHashError(err error) bool {
_, ok := err.(BadHashError)
return ok
}
+
+type GasLimitErr struct {
+ Have, Want *big.Int
+}
+
+func IsGasLimitErr(err error) bool {
+ _, ok := err.(*GasLimitErr)
+ return ok
+}
+
+func (err *GasLimitErr) Error() string {
+ return fmt.Sprintf("GasLimit reached. Have %d gas, transaction requires %d", err.Have, err.Want)
+}
diff --git a/core/events.go b/core/events.go
index e142b6dba..8cf230dda 100644
--- a/core/events.go
+++ b/core/events.go
@@ -20,8 +20,8 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/core/vm"
)
// TxPreEvent is posted when a transaction enters the transaction pool.
@@ -42,23 +42,23 @@ type RemovedTransactionEvent struct{ Txs types.Transactions }
// ChainSplit is posted when a new head is detected
type ChainSplitEvent struct {
Block *types.Block
- Logs state.Logs
+ Logs vm.Logs
}
type ChainEvent struct {
Block *types.Block
Hash common.Hash
- Logs state.Logs
+ Logs vm.Logs
}
type ChainSideEvent struct {
Block *types.Block
- Logs state.Logs
+ Logs vm.Logs
}
type PendingBlockEvent struct {
Block *types.Block
- Logs state.Logs
+ Logs vm.Logs
}
type ChainUncleEvent struct {
diff --git a/core/execution.go b/core/execution.go
index 3a136515d..fd8464f6e 100644
--- a/core/execution.go
+++ b/core/execution.go
@@ -20,105 +20,94 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
-// Execution is the execution environment for the given call or create action.
-type Execution struct {
- env vm.Environment
- address *common.Address
- input []byte
- evm vm.VirtualMachine
-
- Gas, price, value *big.Int
-}
-
-// NewExecution returns a new execution environment that handles all calling
-// and creation logic defined by the YP.
-func NewExecution(env vm.Environment, address *common.Address, input []byte, gas, gasPrice, value *big.Int) *Execution {
- exe := &Execution{env: env, address: address, input: input, Gas: gas, price: gasPrice, value: value}
- exe.evm = vm.NewVm(env)
- return exe
+// Call executes within the given contract
+func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) {
+ ret, _, err = exec(env, caller, &addr, &addr, input, env.Db().GetCode(addr), gas, gasPrice, value)
+ return ret, err
}
-// Call executes within the given context
-func (self *Execution) Call(codeAddr common.Address, caller vm.ContextRef) ([]byte, error) {
- // Retrieve the executing code
- code := self.env.State().GetCode(codeAddr)
-
- return self.exec(&codeAddr, code, caller)
+// CallCode executes the given address' code as the given contract address
+func CallCode(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) {
+ prev := caller.Address()
+ ret, _, err = exec(env, caller, &prev, &addr, input, env.Db().GetCode(addr), gas, gasPrice, value)
+ return ret, err
}
-// Create creates a new contract and runs the initialisation procedure of the
-// contract. This returns the returned code for the contract and is stored
-// elsewhere.
-func (self *Execution) Create(caller vm.ContextRef) (ret []byte, err error, account *state.StateObject) {
- // Input must be nil for create
- code := self.input
- self.input = nil
- ret, err = self.exec(nil, code, caller)
+// Create creates a new contract with the given code
+func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPrice, value *big.Int) (ret []byte, address common.Address, err error) {
+ ret, address, err = exec(env, caller, nil, nil, nil, code, gas, gasPrice, value)
// Here we get an error if we run into maximum stack depth,
// See: https://github.com/ethereum/yellowpaper/pull/131
// and YP definitions for CREATE instruction
if err != nil {
- return nil, err, nil
+ return nil, address, err
}
- account = self.env.State().GetStateObject(*self.address)
- return
+ return ret, address, err
}
-// exec executes the given code and executes within the contextAddr context.
-func (self *Execution) exec(contextAddr *common.Address, code []byte, caller vm.ContextRef) (ret []byte, err error) {
- env := self.env
- evm := self.evm
+func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) {
+ evm := vm.NewVm(env)
+
// Depth check execution. Fail if we're trying to execute above the
// limit.
if env.Depth() > int(params.CallCreateDepth.Int64()) {
- caller.ReturnGas(self.Gas, self.price)
+ caller.ReturnGas(gas, gasPrice)
- return nil, vm.DepthError
+ return nil, common.Address{}, vm.DepthError
}
- if !env.CanTransfer(env.State().GetStateObject(caller.Address()), self.value) {
- caller.ReturnGas(self.Gas, self.price)
+ if !env.CanTransfer(caller.Address(), value) {
+ caller.ReturnGas(gas, gasPrice)
- return nil, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", self.value, env.State().GetBalance(caller.Address()))
+ return nil, common.Address{}, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address()))
}
var createAccount bool
- if self.address == nil {
+ if address == nil {
// Generate a new address
- nonce := env.State().GetNonce(caller.Address())
- env.State().SetNonce(caller.Address(), nonce+1)
+ nonce := env.Db().GetNonce(caller.Address())
+ env.Db().SetNonce(caller.Address(), nonce+1)
- addr := crypto.CreateAddress(caller.Address(), nonce)
+ addr = crypto.CreateAddress(caller.Address(), nonce)
- self.address = &addr
+ address = &addr
createAccount = true
}
- snapshot := env.State().Copy()
+ snapshot := env.MakeSnapshot()
var (
- from = env.State().GetStateObject(caller.Address())
- to *state.StateObject
+ from = env.Db().GetAccount(caller.Address())
+ to vm.Account
)
if createAccount {
- to = env.State().CreateAccount(*self.address)
+ to = env.Db().CreateAccount(*address)
} else {
- to = env.State().GetOrNewStateObject(*self.address)
+ if !env.Db().Exist(*address) {
+ to = env.Db().CreateAccount(*address)
+ } else {
+ to = env.Db().GetAccount(*address)
+ }
}
- vm.Transfer(from, to, self.value)
+ env.Transfer(from, to, value)
- context := vm.NewContext(caller, to, self.value, self.Gas, self.price)
- context.SetCallCode(contextAddr, code)
+ contract := vm.NewContract(caller, to, value, gas, gasPrice)
+ contract.SetCallCode(codeAddr, code)
- ret, err = evm.Run(context, self.input)
+ ret, err = evm.Run(contract, input)
if err != nil {
- env.State().Set(snapshot)
+ env.SetSnapshot(snapshot) //env.Db().Set(snapshot)
}
- return
+ return ret, addr, err
+}
+
+// generic transfer method
+func Transfer(from, to vm.Account, amount *big.Int) {
+ from.SubBalance(amount)
+ to.AddBalance(amount)
}
diff --git a/core/filter.go b/core/filter.go
deleted file mode 100644
index b328ffff3..000000000
--- a/core/filter.go
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright 2014 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 (
- "math"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/logger"
- "github.com/ethereum/go-ethereum/logger/glog"
-)
-
-type AccountChange struct {
- Address, StateAddress []byte
-}
-
-// Filtering interface
-type Filter struct {
- eth Backend
- earliest int64
- latest int64
- skip int
- address []common.Address
- max int
- topics [][]common.Hash
-
- BlockCallback func(*types.Block, state.Logs)
- TransactionCallback func(*types.Transaction)
- LogsCallback func(state.Logs)
-}
-
-// Create a new filter which uses a bloom filter on blocks to figure out whether a particular block
-// is interesting or not.
-func NewFilter(eth Backend) *Filter {
- return &Filter{eth: eth}
-}
-
-// Set the earliest and latest block for filtering.
-// -1 = latest block (i.e., the current block)
-// hash = particular hash from-to
-func (self *Filter) SetEarliestBlock(earliest int64) {
- self.earliest = earliest
-}
-
-func (self *Filter) SetLatestBlock(latest int64) {
- self.latest = latest
-}
-
-func (self *Filter) SetAddress(addr []common.Address) {
- self.address = addr
-}
-
-func (self *Filter) SetTopics(topics [][]common.Hash) {
- self.topics = topics
-}
-
-func (self *Filter) SetMax(max int) {
- self.max = max
-}
-
-func (self *Filter) SetSkip(skip int) {
- self.skip = skip
-}
-
-// Run filters logs with the current parameters set
-func (self *Filter) Find() state.Logs {
- earliestBlock := self.eth.ChainManager().CurrentBlock()
- var earliestBlockNo uint64 = uint64(self.earliest)
- if self.earliest == -1 {
- earliestBlockNo = earliestBlock.NumberU64()
- }
- var latestBlockNo uint64 = uint64(self.latest)
- if self.latest == -1 {
- latestBlockNo = earliestBlock.NumberU64()
- }
-
- var (
- logs state.Logs
- block = self.eth.ChainManager().GetBlockByNumber(latestBlockNo)
- )
-
-done:
- for i := 0; block != nil; i++ {
- // Quit on latest
- switch {
- case block.NumberU64() == 0:
- break done
- case block.NumberU64() < earliestBlockNo:
- break done
- case self.max <= len(logs):
- break done
- }
-
- // Use bloom filtering to see if this block is interesting given the
- // current parameters
- if self.bloomFilter(block) {
- // Get the logs of the block
- unfiltered, err := self.eth.BlockProcessor().GetLogs(block)
- if err != nil {
- glog.V(logger.Warn).Infoln("err: filter get logs ", err)
-
- break
- }
-
- logs = append(logs, self.FilterLogs(unfiltered)...)
- }
-
- block = self.eth.ChainManager().GetBlock(block.ParentHash())
- }
-
- skip := int(math.Min(float64(len(logs)), float64(self.skip)))
-
- return logs[skip:]
-}
-
-func includes(addresses []common.Address, a common.Address) bool {
- for _, addr := range addresses {
- if addr == a {
- return true
- }
- }
-
- return false
-}
-
-func (self *Filter) FilterLogs(logs state.Logs) state.Logs {
- var ret state.Logs
-
- // Filter the logs for interesting stuff
-Logs:
- for _, log := range logs {
- if len(self.address) > 0 && !includes(self.address, log.Address) {
- continue
- }
-
- logTopics := make([]common.Hash, len(self.topics))
- copy(logTopics, log.Topics)
-
- // If the to filtered topics is greater than the amount of topics in
- // logs, skip.
- if len(self.topics) > len(log.Topics) {
- continue Logs
- }
-
- for i, topics := range self.topics {
- var match bool
- for _, topic := range topics {
- // common.Hash{} is a match all (wildcard)
- if (topic == common.Hash{}) || log.Topics[i] == topic {
- match = true
- break
- }
- }
-
- if !match {
- continue Logs
- }
-
- }
-
- ret = append(ret, log)
- }
-
- return ret
-}
-
-func (self *Filter) bloomFilter(block *types.Block) bool {
- if len(self.address) > 0 {
- var included bool
- for _, addr := range self.address {
- if types.BloomLookup(block.Bloom(), addr) {
- included = true
- break
- }
- }
-
- if !included {
- return false
- }
- }
-
- for _, sub := range self.topics {
- var included bool
- for _, topic := range sub {
- if (topic == common.Hash{}) || types.BloomLookup(block.Bloom(), topic) {
- included = true
- break
- }
- }
- if !included {
- return false
- }
- }
-
- return true
-}
diff --git a/core/genesis.go b/core/genesis.go
index b2346da65..dac5de92f 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -60,7 +60,8 @@ func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block,
return nil, err
}
- statedb := state.New(common.Hash{}, chainDb)
+ // creating with empty hash always works
+ statedb, _ := state.New(common.Hash{}, chainDb)
for addr, account := range genesis.Alloc {
address := common.HexToAddress(addr)
statedb.AddBalance(address, common.String2Big(account.Balance))
@@ -69,7 +70,7 @@ func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block,
statedb.SetState(address, common.HexToHash(key), common.HexToHash(value))
}
}
- statedb.SyncObjects()
+ root, stateBatch := statedb.CommitBatch()
difficulty := common.String2Big(genesis.Difficulty)
block := types.NewBlock(&types.Header{
@@ -81,7 +82,7 @@ func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block,
Difficulty: difficulty,
MixDigest: common.HexToHash(genesis.Mixhash),
Coinbase: common.HexToAddress(genesis.Coinbase),
- Root: statedb.Root(),
+ Root: root,
}, nil, nil, nil)
if block := GetBlock(chainDb, block.Hash()); block != nil {
@@ -92,14 +93,19 @@ func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block,
}
return block, nil
}
- statedb.Sync()
+ if err := stateBatch.Write(); err != nil {
+ return nil, fmt.Errorf("cannot write state: %v", err)
+ }
if err := WriteTd(chainDb, block.Hash(), difficulty); err != nil {
return nil, err
}
if err := WriteBlock(chainDb, block); err != nil {
return nil, err
}
+ if err := PutBlockReceipts(chainDb, block.Hash(), nil); err != nil {
+ return nil, err
+ }
if err := WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()); err != nil {
return nil, err
}
@@ -110,17 +116,19 @@ func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block,
}
// GenesisBlockForTesting creates a block in which addr has the given wei balance.
-// The state trie of the block is written to db.
+// The state trie of the block is written to db. the passed db needs to contain a state root
func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block {
- statedb := state.New(common.Hash{}, db)
+ statedb, _ := state.New(common.Hash{}, db)
obj := statedb.GetOrNewStateObject(addr)
obj.SetBalance(balance)
- statedb.SyncObjects()
- statedb.Sync()
+ root, err := statedb.Commit()
+ if err != nil {
+ panic(fmt.Sprintf("cannot write state: %v", err))
+ }
block := types.NewBlock(&types.Header{
Difficulty: params.GenesisDifficulty,
GasLimit: params.GenesisGasLimit,
- Root: statedb.Root(),
+ Root: root,
}, nil, nil, nil)
return block
}
@@ -152,6 +160,27 @@ func WriteGenesisBlockForTesting(db ethdb.Database, accounts ...GenesisAccount)
func WriteTestNetGenesisBlock(chainDb ethdb.Database, nonce uint64) (*types.Block, error) {
testGenesis := fmt.Sprintf(`{
+ "nonce": "0x%x",
+ "difficulty": "0x20000",
+ "mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
+ "coinbase": "0x0000000000000000000000000000000000000000",
+ "timestamp": "0x00",
+ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "extraData": "0x",
+ "gasLimit": "0x2FEFD8",
+ "alloc": {
+ "0000000000000000000000000000000000000001": { "balance": "1" },
+ "0000000000000000000000000000000000000002": { "balance": "1" },
+ "0000000000000000000000000000000000000003": { "balance": "1" },
+ "0000000000000000000000000000000000000004": { "balance": "1" },
+ "102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }
+ }
+}`, types.EncodeNonce(nonce))
+ return WriteGenesisBlock(chainDb, strings.NewReader(testGenesis))
+}
+
+func WriteOlympicGenesisBlock(chainDb ethdb.Database, nonce uint64) (*types.Block, error) {
+ testGenesis := fmt.Sprintf(`{
"nonce":"0x%x",
"gasLimit":"0x%x",
"difficulty":"0x%x",
diff --git a/core/helper_test.go b/core/helper_test.go
index 81ea6fc22..fd6a5491c 100644
--- a/core/helper_test.go
+++ b/core/helper_test.go
@@ -34,7 +34,7 @@ type TestManager struct {
db ethdb.Database
txPool *TxPool
- blockChain *ChainManager
+ blockChain *BlockChain
Blocks []*types.Block
}
@@ -54,7 +54,7 @@ func (s *TestManager) Peers() *list.List {
return list.New()
}
-func (s *TestManager) ChainManager() *ChainManager {
+func (s *TestManager) BlockChain() *BlockChain {
return s.blockChain
}
@@ -89,7 +89,7 @@ func NewTestManager() *TestManager {
testManager.eventMux = new(event.TypeMux)
testManager.db = db
// testManager.txPool = NewTxPool(testManager)
- // testManager.blockChain = NewChainManager(testManager)
+ // testManager.blockChain = NewBlockChain(testManager)
// testManager.stateManager = NewStateManager(testManager)
return testManager
diff --git a/core/manager.go b/core/manager.go
index 0f108a6de..289c87c11 100644
--- a/core/manager.go
+++ b/core/manager.go
@@ -26,7 +26,7 @@ import (
type Backend interface {
AccountManager() *accounts.Manager
BlockProcessor() *BlockProcessor
- ChainManager() *ChainManager
+ BlockChain() *BlockChain
TxPool() *TxPool
ChainDb() ethdb.Database
DappDb() ethdb.Database
diff --git a/core/state/errors.go b/core/state/errors.go
deleted file mode 100644
index a08ed2d23..000000000
--- a/core/state/errors.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2014 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 state
-
-import (
- "fmt"
- "math/big"
-)
-
-type GasLimitErr struct {
- Message string
- Is, Max *big.Int
-}
-
-func IsGasLimitErr(err error) bool {
- _, ok := err.(*GasLimitErr)
-
- return ok
-}
-func (err *GasLimitErr) Error() string {
- return err.Message
-}
-func GasLimitError(is, max *big.Int) *GasLimitErr {
- return &GasLimitErr{Message: fmt.Sprintf("GasLimit error. Max %s, transaction would take it to %s", max, is), Is: is, Max: max}
-}
diff --git a/core/state/managed_state_test.go b/core/state/managed_state_test.go
index 58e77d842..0b53a42c5 100644
--- a/core/state/managed_state_test.go
+++ b/core/state/managed_state_test.go
@@ -27,7 +27,7 @@ var addr = common.BytesToAddress([]byte("test"))
func create() (*ManagedState, *account) {
db, _ := ethdb.NewMemDatabase()
- statedb := New(common.Hash{}, db)
+ statedb, _ := New(common.Hash{}, db)
ms := ManageState(statedb)
so := &StateObject{address: addr, nonce: 100}
ms.StateDB.stateObjects[addr.Str()] = so
diff --git a/core/state/state_object.go b/core/state/state_object.go
index 353f2357b..c06e3d227 100644
--- a/core/state/state_object.go
+++ b/core/state/state_object.go
@@ -75,11 +75,6 @@ type StateObject struct {
// Cached storage (flushed when updated)
storage Storage
- // Total gas pool is the total amount of gas currently
- // left if this object is the coinbase. Gas is directly
- // purchased of the coinbase.
- gasPool *big.Int
-
// Mark for deletion
// When an object is marked for deletion it will be delete from the trie
// during the "update" phase of the state transition
@@ -89,16 +84,13 @@ type StateObject struct {
}
func NewStateObject(address common.Address, db ethdb.Database) *StateObject {
- object := &StateObject{db: db, address: address, balance: new(big.Int), gasPool: new(big.Int), dirty: true}
- object.trie = trie.NewSecure((common.Hash{}).Bytes(), db)
+ object := &StateObject{db: db, address: address, balance: new(big.Int), dirty: true}
+ object.trie, _ = trie.NewSecure(common.Hash{}, db)
object.storage = make(Storage)
- object.gasPool = new(big.Int)
-
return object
}
func NewStateObjectFromBytes(address common.Address, data []byte, db ethdb.Database) *StateObject {
- // TODO clean me up
var extobject struct {
Nonce uint64
Balance *big.Int
@@ -107,7 +99,13 @@ func NewStateObjectFromBytes(address common.Address, data []byte, db ethdb.Datab
}
err := rlp.Decode(bytes.NewReader(data), &extobject)
if err != nil {
- fmt.Println(err)
+ glog.Errorf("can't decode state object %x: %v", address, err)
+ return nil
+ }
+ trie, err := trie.NewSecure(extobject.Root, db)
+ if err != nil {
+ // TODO: bubble this up or panic
+ glog.Errorf("can't create account trie with root %x: %v", extobject.Root[:], err)
return nil
}
@@ -115,11 +113,9 @@ func NewStateObjectFromBytes(address common.Address, data []byte, db ethdb.Datab
object.nonce = extobject.Nonce
object.balance = extobject.Balance
object.codeHash = extobject.CodeHash
- object.trie = trie.NewSecure(extobject.Root[:], db)
+ object.trie = trie
object.storage = make(map[string]common.Hash)
- object.gasPool = new(big.Int)
object.code, _ = db.Get(extobject.CodeHash)
-
return object
}
@@ -206,40 +202,9 @@ func (c *StateObject) St() Storage {
return c.storage
}
-//
-// Gas setters and getters
-//
-
// Return the gas back to the origin. Used by the Virtual machine or Closures
func (c *StateObject) ReturnGas(gas, price *big.Int) {}
-func (self *StateObject) SetGasLimit(gasLimit *big.Int) {
- self.gasPool = new(big.Int).Set(gasLimit)
-
- if glog.V(logger.Core) {
- glog.Infof("%x: gas (+ %v)", self.Address(), self.gasPool)
- }
-}
-
-func (self *StateObject) SubGas(gas, price *big.Int) error {
- if self.gasPool.Cmp(gas) < 0 {
- return GasLimitError(self.gasPool, gas)
- }
-
- self.gasPool.Sub(self.gasPool, gas)
-
- rGas := new(big.Int).Set(gas)
- rGas.Mul(rGas, price)
-
- self.dirty = true
-
- return nil
-}
-
-func (self *StateObject) AddGas(gas, price *big.Int) {
- self.gasPool.Add(self.gasPool, gas)
-}
-
func (self *StateObject) Copy() *StateObject {
stateObject := NewStateObject(self.Address(), self.db)
stateObject.balance.Set(self.balance)
@@ -249,7 +214,6 @@ func (self *StateObject) Copy() *StateObject {
stateObject.code = common.CopyBytes(self.code)
stateObject.initCode = common.CopyBytes(self.initCode)
stateObject.storage = self.storage.Copy()
- stateObject.gasPool.Set(self.gasPool)
stateObject.remove = self.remove
stateObject.dirty = self.dirty
stateObject.deleted = self.deleted
diff --git a/core/state/state_test.go b/core/state/state_test.go
index 60836738e..7ddbe11a1 100644
--- a/core/state/state_test.go
+++ b/core/state/state_test.go
@@ -77,20 +77,19 @@ func (s *StateSuite) TestDump(c *checker.C) {
func (s *StateSuite) SetUpTest(c *checker.C) {
db, _ := ethdb.NewMemDatabase()
- s.state = New(common.Hash{}, db)
+ s.state, _ = New(common.Hash{}, db)
}
func TestNull(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
- state := New(common.Hash{}, db)
+ state, _ := New(common.Hash{}, db)
address := common.HexToAddress("0x823140710bf13990e4500136726d8b55")
state.CreateAccount(address)
//value := common.FromHex("0x823140710bf13990e4500136726d8b55")
var value common.Hash
state.SetState(address, common.Hash{}, value)
- state.SyncIntermediate()
- state.Sync()
+ state.Commit()
value = state.GetState(address, common.Hash{})
if !common.EmptyHash(value) {
t.Errorf("expected empty hash. got %x", value)
@@ -123,7 +122,7 @@ func (s *StateSuite) TestSnapshot(c *checker.C) {
// printing/logging in tests (-check.vv does not work)
func TestSnapshot2(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
- state := New(common.Hash{}, db)
+ state, _ := New(common.Hash{}, db)
stateobjaddr0 := toAddr([]byte("so0"))
stateobjaddr1 := toAddr([]byte("so1"))
@@ -139,7 +138,6 @@ func TestSnapshot2(t *testing.T) {
so0 := state.GetStateObject(stateobjaddr0)
so0.balance = big.NewInt(42)
so0.nonce = 43
- so0.gasPool = big.NewInt(44)
so0.code = []byte{'c', 'a', 'f', 'e'}
so0.codeHash = so0.CodeHash()
so0.remove = true
@@ -151,7 +149,6 @@ func TestSnapshot2(t *testing.T) {
so1 := state.GetStateObject(stateobjaddr1)
so1.balance = big.NewInt(52)
so1.nonce = 53
- so1.gasPool = big.NewInt(54)
so1.code = []byte{'c', 'a', 'f', 'e', '2'}
so1.codeHash = so1.CodeHash()
so1.remove = true
@@ -208,9 +205,6 @@ func compareStateObjects(so0, so1 *StateObject, t *testing.T) {
}
}
- if so0.gasPool.Cmp(so1.gasPool) != 0 {
- t.Fatalf("GasPool mismatch: have %v, want %v", so0.gasPool, so1.gasPool)
- }
if so0.remove != so1.remove {
t.Fatalf("Remove mismatch: have %v, want %v", so0.remove, so1.remove)
}
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 24f97e32a..a9de71409 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -21,12 +21,17 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/trie"
)
+// The starting nonce determines the default nonce when new accounts are being
+// created.
+var StartingNonce uint64
+
// StateDBs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve:
@@ -35,7 +40,6 @@ import (
type StateDB struct {
db ethdb.Database
trie *trie.SecureTrie
- root common.Hash
stateObjects map[string]*StateObject
@@ -43,18 +47,24 @@ type StateDB struct {
thash, bhash common.Hash
txIndex int
- logs map[common.Hash]Logs
+ logs map[common.Hash]vm.Logs
logSize uint
}
// Create a new state from a given trie
-func New(root common.Hash, db ethdb.Database) *StateDB {
- trie := trie.NewSecure(root[:], db)
- return &StateDB{root: root, db: db, trie: trie, stateObjects: make(map[string]*StateObject), refund: new(big.Int), logs: make(map[common.Hash]Logs)}
-}
-
-func (self *StateDB) PrintRoot() {
- self.trie.Trie.PrintRoot()
+func New(root common.Hash, db ethdb.Database) (*StateDB, error) {
+ tr, err := trie.NewSecure(root, db)
+ if err != nil {
+ glog.Errorf("can't create state trie with root %x: %v", root[:], err)
+ return nil, err
+ }
+ return &StateDB{
+ db: db,
+ trie: tr,
+ stateObjects: make(map[string]*StateObject),
+ refund: new(big.Int),
+ logs: make(map[common.Hash]vm.Logs),
+ }, nil
}
func (self *StateDB) StartRecord(thash, bhash common.Hash, ti int) {
@@ -63,7 +73,7 @@ func (self *StateDB) StartRecord(thash, bhash common.Hash, ti int) {
self.txIndex = ti
}
-func (self *StateDB) AddLog(log *Log) {
+func (self *StateDB) AddLog(log *vm.Log) {
log.TxHash = self.thash
log.BlockHash = self.bhash
log.TxIndex = uint(self.txIndex)
@@ -72,30 +82,34 @@ func (self *StateDB) AddLog(log *Log) {
self.logSize++
}
-func (self *StateDB) GetLogs(hash common.Hash) Logs {
+func (self *StateDB) GetLogs(hash common.Hash) vm.Logs {
return self.logs[hash]
}
-func (self *StateDB) Logs() Logs {
- var logs Logs
+func (self *StateDB) Logs() vm.Logs {
+ var logs vm.Logs
for _, lgs := range self.logs {
logs = append(logs, lgs...)
}
return logs
}
-func (self *StateDB) Refund(gas *big.Int) {
+func (self *StateDB) AddRefund(gas *big.Int) {
self.refund.Add(self.refund, gas)
}
-/*
- * GETTERS
- */
-
func (self *StateDB) HasAccount(addr common.Address) bool {
return self.GetStateObject(addr) != nil
}
+func (self *StateDB) Exist(addr common.Address) bool {
+ return self.GetStateObject(addr) != nil
+}
+
+func (self *StateDB) GetAccount(addr common.Address) vm.Account {
+ return self.GetStateObject(addr)
+}
+
// Retrieve the balance from the given address or 0 if object not found
func (self *StateDB) GetBalance(addr common.Address) *big.Int {
stateObject := self.GetStateObject(addr)
@@ -196,7 +210,6 @@ func (self *StateDB) UpdateStateObject(stateObject *StateObject) {
if len(stateObject.CodeHash()) > 0 {
self.db.Put(stateObject.CodeHash(), stateObject.code)
}
-
addr := stateObject.Address()
self.trie.Update(addr[:], stateObject.RlpEncode())
}
@@ -207,6 +220,7 @@ func (self *StateDB) DeleteStateObject(stateObject *StateObject) {
addr := stateObject.Address()
self.trie.Delete(addr[:])
+ //delete(self.stateObjects, addr.Str())
}
// Retrieve a state object given my the address. Nil if not found
@@ -239,7 +253,7 @@ func (self *StateDB) SetStateObject(object *StateObject) {
func (self *StateDB) GetOrNewStateObject(addr common.Address) *StateObject {
stateObject := self.GetStateObject(addr)
if stateObject == nil || stateObject.deleted {
- stateObject = self.CreateAccount(addr)
+ stateObject = self.CreateStateObject(addr)
}
return stateObject
@@ -252,13 +266,14 @@ func (self *StateDB) newStateObject(addr common.Address) *StateObject {
}
stateObject := NewStateObject(addr, self.db)
+ stateObject.SetNonce(StartingNonce)
self.stateObjects[addr.Str()] = stateObject
return stateObject
}
// Creates creates a new state object and takes ownership. This is different from "NewStateObject"
-func (self *StateDB) CreateAccount(addr common.Address) *StateObject {
+func (self *StateDB) CreateStateObject(addr common.Address) *StateObject {
// Get previous (if any)
so := self.GetStateObject(addr)
// Create a new one
@@ -272,12 +287,17 @@ func (self *StateDB) CreateAccount(addr common.Address) *StateObject {
return newSo
}
+func (self *StateDB) CreateAccount(addr common.Address) vm.Account {
+ return self.CreateStateObject(addr)
+}
+
//
// Setting, copying of the state methods
//
func (self *StateDB) Copy() *StateDB {
- state := New(common.Hash{}, self.db)
+ // ignore error - we assume state-to-be-copied always exists
+ state, _ := New(common.Hash{}, self.db)
state.trie = self.trie
for k, stateObject := range self.stateObjects {
state.stateObjects[k] = stateObject.Copy()
@@ -286,7 +306,7 @@ func (self *StateDB) Copy() *StateDB {
state.refund.Set(self.refund)
for hash, logs := range self.logs {
- state.logs[hash] = make(Logs, len(logs))
+ state.logs[hash] = make(vm.Logs, len(logs))
copy(state.logs[hash], logs)
}
state.logSize = self.logSize
@@ -303,65 +323,71 @@ func (self *StateDB) Set(state *StateDB) {
self.logSize = state.logSize
}
-func (s *StateDB) Root() common.Hash {
- return common.BytesToHash(s.trie.Root())
-}
-
-// Syncs the trie and all siblings
-func (s *StateDB) Sync() {
- // Sync all nested states
- for _, stateObject := range s.stateObjects {
- stateObject.trie.Commit()
- }
-
- s.trie.Commit()
-
- s.Empty()
-}
-
-func (self *StateDB) Empty() {
- self.stateObjects = make(map[string]*StateObject)
- self.refund = new(big.Int)
-}
-
-func (self *StateDB) Refunds() *big.Int {
+func (self *StateDB) GetRefund() *big.Int {
return self.refund
}
-// SyncIntermediate updates the intermediate state and all mid steps
-func (self *StateDB) SyncIntermediate() {
- self.refund = new(big.Int)
-
- for _, stateObject := range self.stateObjects {
+// IntermediateRoot computes the current root hash of the state trie.
+// It is called in between transactions to get the root hash that
+// goes into transaction receipts.
+func (s *StateDB) IntermediateRoot() common.Hash {
+ s.refund = new(big.Int)
+ for _, stateObject := range s.stateObjects {
if stateObject.dirty {
if stateObject.remove {
- self.DeleteStateObject(stateObject)
+ s.DeleteStateObject(stateObject)
} else {
stateObject.Update()
-
- self.UpdateStateObject(stateObject)
+ s.UpdateStateObject(stateObject)
}
stateObject.dirty = false
}
}
+ return s.trie.Hash()
+}
+
+// Commit commits all state changes to the database.
+func (s *StateDB) Commit() (root common.Hash, err error) {
+ return s.commit(s.db)
}
-// SyncObjects syncs the changed objects to the trie
-func (self *StateDB) SyncObjects() {
- self.trie = trie.NewSecure(self.root[:], self.db)
+// CommitBatch commits all state changes to a write batch but does not
+// execute the batch. It is used to validate state changes against
+// the root hash stored in a block.
+func (s *StateDB) CommitBatch() (root common.Hash, batch ethdb.Batch) {
+ batch = s.db.NewBatch()
+ root, _ = s.commit(batch)
+ return root, batch
+}
- self.refund = new(big.Int)
+func (s *StateDB) commit(db trie.DatabaseWriter) (common.Hash, error) {
+ s.refund = new(big.Int)
- for _, stateObject := range self.stateObjects {
+ for _, stateObject := range s.stateObjects {
if stateObject.remove {
- self.DeleteStateObject(stateObject)
+ // If the object has been removed, don't bother syncing it
+ // and just mark it for deletion in the trie.
+ s.DeleteStateObject(stateObject)
} else {
+ // Write any storage changes in the state object to its trie.
stateObject.Update()
-
- self.UpdateStateObject(stateObject)
+ // Commit the trie of the object to the batch.
+ // This updates the trie root internally, so
+ // getting the root hash of the storage trie
+ // through UpdateStateObject is fast.
+ if _, err := stateObject.trie.CommitTo(db); err != nil {
+ return common.Hash{}, err
+ }
+ // Update the object in the account trie.
+ s.UpdateStateObject(stateObject)
}
stateObject.dirty = false
}
+ return s.trie.CommitTo(db)
+}
+
+func (self *StateDB) Refunds() *big.Int {
+ return self.refund
}
// Debug stuff
diff --git a/core/state/sync.go b/core/state/sync.go
new file mode 100644
index 000000000..ef2b4b84c
--- /dev/null
+++ b/core/state/sync.go
@@ -0,0 +1,70 @@
+// 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 state
+
+import (
+ "bytes"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/trie"
+)
+
+// StateSync is the main state synchronisation scheduler, which provides yet the
+// unknown state hashes to retrieve, accepts node data associated with said hashes
+// and reconstructs the state database step by step until all is done.
+type StateSync trie.TrieSync
+
+// NewStateSync create a new state trie download scheduler.
+func NewStateSync(root common.Hash, database ethdb.Database) *StateSync {
+ var syncer *trie.TrieSync
+
+ callback := func(leaf []byte, parent common.Hash) error {
+ var obj struct {
+ Nonce uint64
+ Balance *big.Int
+ Root common.Hash
+ CodeHash []byte
+ }
+ if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil {
+ return err
+ }
+ syncer.AddSubTrie(obj.Root, 64, parent, nil)
+ syncer.AddRawEntry(common.BytesToHash(obj.CodeHash), 64, parent)
+
+ return nil
+ }
+ syncer = trie.NewTrieSync(root, database, callback)
+ return (*StateSync)(syncer)
+}
+
+// Missing retrieves the known missing nodes from the state trie for retrieval.
+func (s *StateSync) Missing(max int) []common.Hash {
+ return (*trie.TrieSync)(s).Missing(max)
+}
+
+// Process injects a batch of retrieved trie nodes data.
+func (s *StateSync) Process(list []trie.SyncResult) (int, error) {
+ return (*trie.TrieSync)(s).Process(list)
+}
+
+// Pending returns the number of state entries currently pending for download.
+func (s *StateSync) Pending() int {
+ return (*trie.TrieSync)(s).Pending()
+}
diff --git a/core/state/sync_test.go b/core/state/sync_test.go
new file mode 100644
index 000000000..0dab372ba
--- /dev/null
+++ b/core/state/sync_test.go
@@ -0,0 +1,238 @@
+// 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 state
+
+import (
+ "bytes"
+ "math/big"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/trie"
+)
+
+// testAccount is the data associated with an account used by the state tests.
+type testAccount struct {
+ address common.Address
+ balance *big.Int
+ nonce uint64
+ code []byte
+}
+
+// makeTestState create a sample test state to test node-wise reconstruction.
+func makeTestState() (ethdb.Database, common.Hash, []*testAccount) {
+ // Create an empty state
+ db, _ := ethdb.NewMemDatabase()
+ state, _ := New(common.Hash{}, db)
+
+ // Fill it with some arbitrary data
+ accounts := []*testAccount{}
+ for i := byte(0); i < 255; i++ {
+ obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i}))
+ acc := &testAccount{address: common.BytesToAddress([]byte{i})}
+
+ obj.AddBalance(big.NewInt(int64(11 * i)))
+ acc.balance = big.NewInt(int64(11 * i))
+
+ obj.SetNonce(uint64(42 * i))
+ acc.nonce = uint64(42 * i)
+
+ if i%3 == 0 {
+ obj.SetCode([]byte{i, i, i, i, i})
+ acc.code = []byte{i, i, i, i, i}
+ }
+ state.UpdateStateObject(obj)
+ accounts = append(accounts, acc)
+ }
+ root, _ := state.Commit()
+
+ // Return the generated state
+ return db, root, accounts
+}
+
+// checkStateAccounts cross references a reconstructed state with an expected
+// account array.
+func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accounts []*testAccount) {
+ state, _ := New(root, db)
+ for i, acc := range accounts {
+
+ if balance := state.GetBalance(acc.address); balance.Cmp(acc.balance) != 0 {
+ t.Errorf("account %d: balance mismatch: have %v, want %v", i, balance, acc.balance)
+ }
+ if nonce := state.GetNonce(acc.address); nonce != acc.nonce {
+ t.Errorf("account %d: nonce mismatch: have %v, want %v", i, nonce, acc.nonce)
+ }
+ if code := state.GetCode(acc.address); bytes.Compare(code, acc.code) != 0 {
+ t.Errorf("account %d: code mismatch: have %x, want %x", i, code, acc.code)
+ }
+ }
+}
+
+// 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 {
+ t.Errorf("content requested for empty state: %v", req)
+ }
+}
+
+// Tests that given a root hash, a state can sync iteratively on a single thread,
+// requesting retrieval tasks and returning all of them in one go.
+func TestIterativeStateSyncIndividual(t *testing.T) { testIterativeStateSync(t, 1) }
+func TestIterativeStateSyncBatched(t *testing.T) { testIterativeStateSync(t, 100) }
+
+func testIterativeStateSync(t *testing.T, batch int) {
+ // Create a random state to copy
+ srcDb, srcRoot, srcAccounts := makeTestState()
+
+ // Create a destination state and sync with the scheduler
+ dstDb, _ := ethdb.NewMemDatabase()
+ sched := NewStateSync(srcRoot, dstDb)
+
+ queue := append([]common.Hash{}, sched.Missing(batch)...)
+ for len(queue) > 0 {
+ results := make([]trie.SyncResult, len(queue))
+ for i, hash := range queue {
+ data, err := srcDb.Get(hash.Bytes())
+ if err != nil {
+ t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
+ }
+ results[i] = trie.SyncResult{hash, data}
+ }
+ if index, err := sched.Process(results); err != nil {
+ t.Fatalf("failed to process result #%d: %v", index, err)
+ }
+ queue = append(queue[:0], sched.Missing(batch)...)
+ }
+ // Cross check that the two states are in sync
+ checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
+}
+
+// Tests that the trie scheduler can correctly reconstruct the state even if only
+// partial results are returned, and the others sent only later.
+func TestIterativeDelayedStateSync(t *testing.T) {
+ // Create a random state to copy
+ srcDb, srcRoot, srcAccounts := makeTestState()
+
+ // Create a destination state and sync with the scheduler
+ dstDb, _ := ethdb.NewMemDatabase()
+ sched := NewStateSync(srcRoot, dstDb)
+
+ queue := append([]common.Hash{}, sched.Missing(0)...)
+ for len(queue) > 0 {
+ // Sync only half of the scheduled nodes
+ results := make([]trie.SyncResult, len(queue)/2+1)
+ for i, hash := range queue[:len(results)] {
+ data, err := srcDb.Get(hash.Bytes())
+ if err != nil {
+ t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
+ }
+ results[i] = trie.SyncResult{hash, data}
+ }
+ if index, err := sched.Process(results); err != nil {
+ t.Fatalf("failed to process result #%d: %v", index, err)
+ }
+ queue = append(queue[len(results):], sched.Missing(0)...)
+ }
+ // Cross check that the two states are in sync
+ checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
+}
+
+// Tests that given a root hash, a trie can sync iteratively on a single thread,
+// requesting retrieval tasks and returning all of them in one go, however in a
+// random order.
+func TestIterativeRandomStateSyncIndividual(t *testing.T) { testIterativeRandomStateSync(t, 1) }
+func TestIterativeRandomStateSyncBatched(t *testing.T) { testIterativeRandomStateSync(t, 100) }
+
+func testIterativeRandomStateSync(t *testing.T, batch int) {
+ // Create a random state to copy
+ srcDb, srcRoot, srcAccounts := makeTestState()
+
+ // Create a destination state and sync with the scheduler
+ dstDb, _ := ethdb.NewMemDatabase()
+ sched := NewStateSync(srcRoot, dstDb)
+
+ queue := make(map[common.Hash]struct{})
+ for _, hash := range sched.Missing(batch) {
+ queue[hash] = struct{}{}
+ }
+ for len(queue) > 0 {
+ // Fetch all the queued nodes in a random order
+ results := make([]trie.SyncResult, 0, len(queue))
+ for hash, _ := range queue {
+ data, err := srcDb.Get(hash.Bytes())
+ if err != nil {
+ t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
+ }
+ results = append(results, trie.SyncResult{hash, data})
+ }
+ // Feed the retrieved results back and queue new tasks
+ if index, err := sched.Process(results); err != nil {
+ t.Fatalf("failed to process result #%d: %v", index, err)
+ }
+ queue = make(map[common.Hash]struct{})
+ for _, hash := range sched.Missing(batch) {
+ queue[hash] = struct{}{}
+ }
+ }
+ // Cross check that the two states are in sync
+ checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
+}
+
+// Tests that the trie scheduler can correctly reconstruct the state even if only
+// partial results are returned (Even those randomly), others sent only later.
+func TestIterativeRandomDelayedStateSync(t *testing.T) {
+ // Create a random state to copy
+ srcDb, srcRoot, srcAccounts := makeTestState()
+
+ // Create a destination state and sync with the scheduler
+ dstDb, _ := ethdb.NewMemDatabase()
+ sched := NewStateSync(srcRoot, dstDb)
+
+ queue := make(map[common.Hash]struct{})
+ for _, hash := range sched.Missing(0) {
+ queue[hash] = struct{}{}
+ }
+ for len(queue) > 0 {
+ // Sync only half of the scheduled nodes, even those in random order
+ results := make([]trie.SyncResult, 0, len(queue)/2+1)
+ for hash, _ := range queue {
+ delete(queue, hash)
+
+ data, err := srcDb.Get(hash.Bytes())
+ if err != nil {
+ t.Fatalf("failed to retrieve node data for %x: %v", hash, err)
+ }
+ results = append(results, trie.SyncResult{hash, data})
+
+ if len(results) >= cap(results) {
+ break
+ }
+ }
+ // Feed the retrieved results back and queue new tasks
+ if index, err := sched.Process(results); err != nil {
+ t.Fatalf("failed to process result #%d: %v", index, err)
+ }
+ for _, hash := range sched.Missing(0) {
+ queue[hash] = struct{}{}
+ }
+ }
+ // Cross check that the two states are in sync
+ checkStateAccounts(t, dstDb, srcRoot, srcAccounts)
+}
diff --git a/core/state_transition.go b/core/state_transition.go
index 6ff7fa1ff..7ecc01d4c 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -21,7 +21,6 @@ import (
"math/big"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
@@ -29,29 +28,30 @@ import (
)
/*
- * 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.
- * 1) Nonce handling
- * 2) Pre pay / buy gas of the coinbase (miner)
- * 3) Create a new state object if the recipient is \0*32
- * 4) Value transfer
- * == If contract creation ==
- * 4a) Attempt to run transaction data
- * 4b) If valid, use result as code for the new state object
- * == end ==
- * 5) Run Script section
- * 6) Derive new state root
- */
+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.
+
+1) Nonce handling
+2) Pre pay gas
+3) Create a new state object if the recipient is \0*32
+4) Value transfer
+== If contract creation ==
+ 4a) Attempt to run transaction data
+ 4b) If valid, use result as code for the new state object
+== end ==
+5) Run Script section
+6) Derive new state root
+*/
type StateTransition struct {
- gp GasPool
+ gp *GasPool
msg Message
gas, gasPrice *big.Int
initialGas *big.Int
value *big.Int
data []byte
- state *state.StateDB
+ state vm.Database
env vm.Environment
}
@@ -94,12 +94,8 @@ func IntrinsicGas(data []byte) *big.Int {
return igas
}
-func ApplyMessage(env vm.Environment, msg Message, gp GasPool) ([]byte, *big.Int, error) {
- return NewStateTransition(env, msg, gp).transitionState()
-}
-
-func NewStateTransition(env vm.Environment, msg Message, gp GasPool) *StateTransition {
- return &StateTransition{
+func ApplyMessage(env vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) {
+ var st = StateTransition{
gp: gp,
env: env,
msg: msg,
@@ -108,18 +104,22 @@ func NewStateTransition(env vm.Environment, msg Message, gp GasPool) *StateTrans
initialGas: new(big.Int),
value: msg.Value(),
data: msg.Data(),
- state: env.State(),
+ state: env.Db(),
}
+ return st.transitionDb()
}
-func (self *StateTransition) From() (*state.StateObject, error) {
+func (self *StateTransition) from() (vm.Account, error) {
f, err := self.msg.From()
if err != nil {
return nil, err
}
- return self.state.GetOrNewStateObject(f), nil
+ if !self.state.Exist(f) {
+ return self.state.CreateAccount(f), nil
+ }
+ return self.state.GetAccount(f), nil
}
-func (self *StateTransition) To() *state.StateObject {
+func (self *StateTransition) to() vm.Account {
if self.msg == nil {
return nil
}
@@ -127,10 +127,14 @@ func (self *StateTransition) To() *state.StateObject {
if to == nil {
return nil // contract creation
}
- return self.state.GetOrNewStateObject(*to)
+
+ if !self.state.Exist(*to) {
+ return self.state.CreateAccount(*to)
+ }
+ return self.state.GetAccount(*to)
}
-func (self *StateTransition) UseGas(amount *big.Int) error {
+func (self *StateTransition) useGas(amount *big.Int) error {
if self.gas.Cmp(amount) < 0 {
return vm.OutOfGasError
}
@@ -139,25 +143,25 @@ func (self *StateTransition) UseGas(amount *big.Int) error {
return nil
}
-func (self *StateTransition) AddGas(amount *big.Int) {
+func (self *StateTransition) addGas(amount *big.Int) {
self.gas.Add(self.gas, amount)
}
-func (self *StateTransition) BuyGas() error {
+func (self *StateTransition) buyGas() error {
mgas := self.msg.Gas()
mgval := new(big.Int).Mul(mgas, self.gasPrice)
- sender, err := self.From()
+ sender, err := self.from()
if err != nil {
return err
}
if sender.Balance().Cmp(mgval) < 0 {
return fmt.Errorf("insufficient ETH for gas (%x). Req %v, has %v", sender.Address().Bytes()[:4], mgval, sender.Balance())
}
- if err = self.gp.SubGas(mgas, self.gasPrice); err != nil {
+ if err = self.gp.SubGas(mgas); err != nil {
return err
}
- self.AddGas(mgas)
+ self.addGas(mgas)
self.initialGas.Set(mgas)
sender.SubBalance(mgval)
return nil
@@ -165,19 +169,20 @@ func (self *StateTransition) BuyGas() error {
func (self *StateTransition) preCheck() (err error) {
msg := self.msg
- sender, err := self.From()
+ sender, err := self.from()
if err != nil {
return err
}
// Make sure this transaction's nonce is correct
- if sender.Nonce() != msg.Nonce() {
- return NonceError(msg.Nonce(), sender.Nonce())
+ //if sender.Nonce() != msg.Nonce() {
+ if n := self.state.GetNonce(sender.Address()); n != msg.Nonce() {
+ return NonceError(msg.Nonce(), n)
}
- // Pre-pay gas / Buy gas of the coinbase account
- if err = self.BuyGas(); err != nil {
- if state.IsGasLimitErr(err) {
+ // Pre-pay gas
+ if err = self.buyGas(); err != nil {
+ if IsGasLimitErr(err) {
return err
}
return InvalidTxError(err)
@@ -186,28 +191,28 @@ func (self *StateTransition) preCheck() (err error) {
return nil
}
-func (self *StateTransition) transitionState() (ret []byte, usedGas *big.Int, err error) {
+func (self *StateTransition) transitionDb() (ret []byte, usedGas *big.Int, err error) {
if err = self.preCheck(); err != nil {
return
}
msg := self.msg
- sender, _ := self.From() // err checked in preCheck
+ sender, _ := self.from() // err checked in preCheck
// Pay intrinsic gas
- if err = self.UseGas(IntrinsicGas(self.data)); err != nil {
+ if err = self.useGas(IntrinsicGas(self.data)); err != nil {
return nil, nil, InvalidTxError(err)
}
vmenv := self.env
- var ref vm.ContextRef
+ var addr common.Address
if MessageCreatesContract(msg) {
- ret, err, ref = vmenv.Create(sender, self.data, self.gas, self.gasPrice, self.value)
+ ret, addr, err = vmenv.Create(sender, self.data, self.gas, self.gasPrice, self.value)
if err == nil {
dataGas := big.NewInt(int64(len(ret)))
dataGas.Mul(dataGas, params.CreateDataGas)
- if err := self.UseGas(dataGas); err == nil {
- ref.SetCode(ret)
+ if err := self.useGas(dataGas); err == nil {
+ self.state.SetCode(addr, ret)
} else {
ret = nil // does not affect consensus but useful for StateTests validations
glog.V(logger.Core).Infoln("Insufficient gas for creating code. Require", dataGas, "and have", self.gas)
@@ -216,8 +221,8 @@ func (self *StateTransition) transitionState() (ret []byte, usedGas *big.Int, er
glog.V(logger.Core).Infoln("VM create err:", err)
} else {
// Increment the nonce for the next transaction
- self.state.SetNonce(sender.Address(), sender.Nonce()+1)
- ret, err = vmenv.Call(sender, self.To().Address(), self.data, self.gas, self.gasPrice, self.value)
+ self.state.SetNonce(sender.Address(), self.state.GetNonce(sender.Address())+1)
+ ret, err = vmenv.Call(sender, self.to().Address(), self.data, self.gas, self.gasPrice, self.value)
glog.V(logger.Core).Infoln("VM call err:", err)
}
@@ -241,17 +246,21 @@ func (self *StateTransition) transitionState() (ret []byte, usedGas *big.Int, er
}
func (self *StateTransition) refundGas() {
- sender, _ := self.From() // err already checked
- // Return remaining gas
+ // Return eth for remaining gas to the sender account,
+ // exchanged at the original rate.
+ sender, _ := self.from() // err already checked
remaining := new(big.Int).Mul(self.gas, self.gasPrice)
sender.AddBalance(remaining)
+ // Apply refund counter, capped to half of the used gas.
uhalf := remaining.Div(self.gasUsed(), common.Big2)
- refund := common.BigMin(uhalf, self.state.Refunds())
+ refund := common.BigMin(uhalf, self.state.GetRefund())
self.gas.Add(self.gas, refund)
self.state.AddBalance(sender.Address(), refund.Mul(refund, self.gasPrice))
- self.gp.AddGas(self.gas, self.gasPrice)
+ // Also return remaining gas to the block gas counter so it is
+ // available for the next transaction.
+ self.gp.AddGas(self.gas)
}
func (self *StateTransition) gasUsed() *big.Int {
diff --git a/core/transaction_pool.go b/core/transaction_pool.go
index 11d0cb490..16f66efdc 100644
--- a/core/transaction_pool.go
+++ b/core/transaction_pool.go
@@ -48,7 +48,7 @@ const (
maxQueued = 64 // max limit of queued txs per address
)
-type stateFn func() *state.StateDB
+type stateFn func() (*state.StateDB, error)
// TxPool contains all currently known transactions. Transactions
// enter the pool when they are received from the network or submitted
@@ -80,7 +80,7 @@ func NewTxPool(eventMux *event.TypeMux, currentStateFn stateFn, gasLimitFn func(
currentState: currentStateFn,
gasLimit: gasLimitFn,
minGasPrice: new(big.Int),
- pendingState: state.ManageState(currentStateFn()),
+ pendingState: nil,
events: eventMux.Subscribe(ChainHeadEvent{}, GasPriceChanged{}, RemovedTransactionEvent{}),
}
go pool.eventLoop()
@@ -93,7 +93,7 @@ func (pool *TxPool) eventLoop() {
// we need to know the new state. The new state will help us determine
// the nonces in the managed state
for ev := range pool.events.Chan() {
- switch ev := ev.(type) {
+ switch ev := ev.Data.(type) {
case ChainHeadEvent:
pool.mu.Lock()
pool.resetState()
@@ -109,7 +109,17 @@ func (pool *TxPool) eventLoop() {
}
func (pool *TxPool) resetState() {
- pool.pendingState = state.ManageState(pool.currentState())
+ currentState, err := pool.currentState()
+ if err != nil {
+ glog.V(logger.Info).Infoln("failed to get current state: %v", err)
+ return
+ }
+ managedState := state.ManageState(currentState)
+ if err != nil {
+ glog.V(logger.Info).Infoln("failed to get managed state: %v", err)
+ return
+ }
+ pool.pendingState = managedState
// validate the pool of pending transactions, this will remove
// any transactions that have been included in the block or
@@ -180,12 +190,16 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
// Make sure the account exist. Non existent accounts
// haven't got funds and well therefor never pass.
- if !pool.currentState().HasAccount(from) {
+ currentState, err := pool.currentState()
+ if err != nil {
+ return err
+ }
+ if !currentState.HasAccount(from) {
return ErrNonExistentAccount
}
// Last but not least check for nonce errors
- if pool.currentState().GetNonce(from) > tx.Nonce() {
+ if currentState.GetNonce(from) > tx.Nonce() {
return ErrNonce
}
@@ -204,7 +218,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error {
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL
- if pool.currentState().GetBalance(from).Cmp(tx.Cost()) < 0 {
+ if currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}
@@ -257,6 +271,11 @@ func (self *TxPool) queueTx(hash common.Hash, tx *types.Transaction) {
// addTx will add a transaction to the pending (processable queue) list of transactions
func (pool *TxPool) addTx(hash common.Hash, addr common.Address, tx *types.Transaction) {
+ // init delayed since tx pool could have been started before any state sync
+ if pool.pendingState == nil {
+ pool.resetState()
+ }
+
if _, ok := pool.pending[hash]; !ok {
pool.pending[hash] = tx
@@ -382,14 +401,22 @@ func (pool *TxPool) RemoveTx(hash common.Hash) {
// checkQueue moves transactions that have become processable to main pool.
func (pool *TxPool) checkQueue() {
- state := pool.pendingState
+ // init delayed since tx pool could have been started before any state sync
+ if pool.pendingState == nil {
+ pool.resetState()
+ }
var addq txQueue
for address, txs := range pool.queue {
// guessed nonce is the nonce currently kept by the tx pool (pending state)
- guessedNonce := state.GetNonce(address)
+ guessedNonce := pool.pendingState.GetNonce(address)
// true nonce is the nonce known by the last state
- trueNonce := pool.currentState().GetNonce(address)
+ currentState, err := pool.currentState()
+ if err != nil {
+ glog.Errorf("could not get current state: %v", err)
+ return
+ }
+ trueNonce := currentState.GetNonce(address)
addq := addq[:0]
for hash, tx := range txs {
if tx.Nonce() < trueNonce {
@@ -434,7 +461,11 @@ func (pool *TxPool) checkQueue() {
// validatePool removes invalid and processed transactions from the main pool.
func (pool *TxPool) validatePool() {
- state := pool.currentState()
+ state, err := pool.currentState()
+ if err != nil {
+ glog.V(logger.Info).Infoln("failed to get current state: %v", err)
+ return
+ }
for hash, tx := range pool.pending {
from, _ := tx.From() // err already checked
// perform light nonce validation
diff --git a/core/transaction_pool_test.go b/core/transaction_pool_test.go
index 37cd20c96..229dcacf3 100644
--- a/core/transaction_pool_test.go
+++ b/core/transaction_pool_test.go
@@ -36,11 +36,13 @@ func transaction(nonce uint64, gaslimit *big.Int, key *ecdsa.PrivateKey) *types.
func setupTxPool() (*TxPool, *ecdsa.PrivateKey) {
db, _ := ethdb.NewMemDatabase()
- statedb := state.New(common.Hash{}, db)
+ statedb, _ := state.New(common.Hash{}, db)
var m event.TypeMux
key, _ := crypto.GenerateKey()
- return NewTxPool(&m, func() *state.StateDB { return statedb }, func() *big.Int { return big.NewInt(1000000) }), key
+ newPool := NewTxPool(&m, func() (*state.StateDB, error) { return statedb, nil }, func() *big.Int { return big.NewInt(1000000) })
+ newPool.resetState()
+ return newPool, key
}
func TestInvalidTransactions(t *testing.T) {
@@ -52,19 +54,20 @@ func TestInvalidTransactions(t *testing.T) {
}
from, _ := tx.From()
- pool.currentState().AddBalance(from, big.NewInt(1))
+ currentState, _ := pool.currentState()
+ currentState.AddBalance(from, big.NewInt(1))
if err := pool.Add(tx); err != ErrInsufficientFunds {
t.Error("expected", ErrInsufficientFunds)
}
balance := new(big.Int).Add(tx.Value(), new(big.Int).Mul(tx.Gas(), tx.GasPrice()))
- pool.currentState().AddBalance(from, balance)
+ currentState.AddBalance(from, balance)
if err := pool.Add(tx); err != ErrIntrinsicGas {
t.Error("expected", ErrIntrinsicGas, "got", err)
}
- pool.currentState().SetNonce(from, 1)
- pool.currentState().AddBalance(from, big.NewInt(0xffffffffffffff))
+ currentState.SetNonce(from, 1)
+ currentState.AddBalance(from, big.NewInt(0xffffffffffffff))
tx = transaction(0, big.NewInt(100000), key)
if err := pool.Add(tx); err != ErrNonce {
t.Error("expected", ErrNonce)
@@ -75,7 +78,8 @@ func TestTransactionQueue(t *testing.T) {
pool, key := setupTxPool()
tx := transaction(0, big.NewInt(100), key)
from, _ := tx.From()
- pool.currentState().AddBalance(from, big.NewInt(1))
+ currentState, _ := pool.currentState()
+ currentState.AddBalance(from, big.NewInt(1))
pool.queueTx(tx.Hash(), tx)
pool.checkQueue()
@@ -85,7 +89,7 @@ func TestTransactionQueue(t *testing.T) {
tx = transaction(1, big.NewInt(100), key)
from, _ = tx.From()
- pool.currentState().SetNonce(from, 2)
+ currentState.SetNonce(from, 2)
pool.queueTx(tx.Hash(), tx)
pool.checkQueue()
if _, ok := pool.pending[tx.Hash()]; ok {
@@ -119,7 +123,8 @@ func TestRemoveTx(t *testing.T) {
pool, key := setupTxPool()
tx := transaction(0, big.NewInt(100), key)
from, _ := tx.From()
- pool.currentState().AddBalance(from, big.NewInt(1))
+ currentState, _ := pool.currentState()
+ currentState.AddBalance(from, big.NewInt(1))
pool.queueTx(tx.Hash(), tx)
pool.addTx(tx.Hash(), from, tx)
if len(pool.queue) != 1 {
@@ -146,7 +151,8 @@ func TestNegativeValue(t *testing.T) {
tx, _ := types.NewTransaction(0, common.Address{}, big.NewInt(-1), big.NewInt(100), big.NewInt(1), nil).SignECDSA(key)
from, _ := tx.From()
- pool.currentState().AddBalance(from, big.NewInt(1))
+ currentState, _ := pool.currentState()
+ currentState.AddBalance(from, big.NewInt(1))
if err := pool.Add(tx); err != ErrNegativeValue {
t.Error("expected", ErrNegativeValue, "got", err)
}
@@ -157,9 +163,10 @@ func TestTransactionChainFork(t *testing.T) {
addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() {
db, _ := ethdb.NewMemDatabase()
- statedb := state.New(common.Hash{}, db)
- pool.currentState = func() *state.StateDB { return statedb }
- pool.currentState().AddBalance(addr, big.NewInt(100000000000000))
+ statedb, _ := state.New(common.Hash{}, db)
+ pool.currentState = func() (*state.StateDB, error) { return statedb, nil }
+ currentState, _ := pool.currentState()
+ currentState.AddBalance(addr, big.NewInt(100000000000000))
pool.resetState()
}
resetState()
@@ -182,9 +189,10 @@ func TestTransactionDoubleNonce(t *testing.T) {
addr := crypto.PubkeyToAddress(key.PublicKey)
resetState := func() {
db, _ := ethdb.NewMemDatabase()
- statedb := state.New(common.Hash{}, db)
- pool.currentState = func() *state.StateDB { return statedb }
- pool.currentState().AddBalance(addr, big.NewInt(100000000000000))
+ statedb, _ := state.New(common.Hash{}, db)
+ pool.currentState = func() (*state.StateDB, error) { return statedb, nil }
+ currentState, _ := pool.currentState()
+ currentState.AddBalance(addr, big.NewInt(100000000000000))
pool.resetState()
}
resetState()
@@ -207,7 +215,8 @@ func TestTransactionDoubleNonce(t *testing.T) {
func TestMissingNonce(t *testing.T) {
pool, key := setupTxPool()
addr := crypto.PubkeyToAddress(key.PublicKey)
- pool.currentState().AddBalance(addr, big.NewInt(100000000000000))
+ currentState, _ := pool.currentState()
+ currentState.AddBalance(addr, big.NewInt(100000000000000))
tx := transaction(1, big.NewInt(100000), key)
if err := pool.add(tx); err != nil {
t.Error("didn't expect error", err)
@@ -224,15 +233,16 @@ func TestNonceRecovery(t *testing.T) {
const n = 10
pool, key := setupTxPool()
addr := crypto.PubkeyToAddress(key.PublicKey)
- pool.currentState().SetNonce(addr, n)
- pool.currentState().AddBalance(addr, big.NewInt(100000000000000))
+ currentState, _ := pool.currentState()
+ currentState.SetNonce(addr, n)
+ currentState.AddBalance(addr, big.NewInt(100000000000000))
pool.resetState()
tx := transaction(n, big.NewInt(100000), key)
if err := pool.Add(tx); err != nil {
t.Error(err)
}
// simulate some weird re-order of transactions and missing nonce(s)
- pool.currentState().SetNonce(addr, n-1)
+ currentState.SetNonce(addr, n-1)
pool.resetState()
if fn := pool.pendingState.GetNonce(addr); fn != n+1 {
t.Errorf("expected nonce to be %d, got %d", n+1, fn)
@@ -243,7 +253,8 @@ func TestRemovedTxEvent(t *testing.T) {
pool, key := setupTxPool()
tx := transaction(0, big.NewInt(1000000), key)
from, _ := tx.From()
- pool.currentState().AddBalance(from, big.NewInt(1000000000000))
+ currentState, _ := pool.currentState()
+ currentState.AddBalance(from, big.NewInt(1000000000000))
pool.eventMux.Post(RemovedTransactionEvent{types.Transactions{tx}})
pool.eventMux.Post(ChainHeadEvent{nil})
if len(pool.pending) != 1 {
diff --git a/core/transaction_util.go b/core/transaction_util.go
index ebe095abb..e2e5b9aee 100644
--- a/core/transaction_util.go
+++ b/core/transaction_util.go
@@ -17,6 +17,8 @@
package core
import (
+ "fmt"
+
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
@@ -32,22 +34,16 @@ var (
)
// PutTransactions stores the transactions in the given database
-func PutTransactions(db ethdb.Database, block *types.Block, txs types.Transactions) {
- batch := new(leveldb.Batch)
- _, batchWrite := db.(*ethdb.LDBDatabase)
+func PutTransactions(db ethdb.Database, block *types.Block, txs types.Transactions) error {
+ batch := db.NewBatch()
for i, tx := range block.Transactions() {
rlpEnc, err := rlp.EncodeToBytes(tx)
if err != nil {
- glog.V(logger.Debug).Infoln("Failed encoding tx", err)
- return
+ return fmt.Errorf("failed encoding tx: %v", err)
}
- if batchWrite {
- batch.Put(tx.Hash().Bytes(), rlpEnc)
- } else {
- db.Put(tx.Hash().Bytes(), rlpEnc)
- }
+ batch.Put(tx.Hash().Bytes(), rlpEnc)
var txExtra struct {
BlockHash common.Hash
@@ -59,22 +55,16 @@ func PutTransactions(db ethdb.Database, block *types.Block, txs types.Transactio
txExtra.Index = uint64(i)
rlpMeta, err := rlp.EncodeToBytes(txExtra)
if err != nil {
- glog.V(logger.Debug).Infoln("Failed encoding tx meta data", err)
- return
+ return fmt.Errorf("failed encoding tx meta data: %v", err)
}
- if batchWrite {
- batch.Put(append(tx.Hash().Bytes(), 0x0001), rlpMeta)
- } else {
- db.Put(append(tx.Hash().Bytes(), 0x0001), rlpMeta)
- }
+ batch.Put(append(tx.Hash().Bytes(), 0x0001), rlpMeta)
}
- if db, ok := db.(*ethdb.LDBDatabase); ok {
- if err := db.LDB().Write(batch, nil); err != nil {
- glog.V(logger.Error).Infoln("db write err:", err)
- }
+ if err := batch.Write(); err != nil {
+ return fmt.Errorf("failed writing tx to db: %v", err)
}
+ return nil
}
func DeleteTransaction(db ethdb.Database, txHash common.Hash) {
@@ -134,13 +124,12 @@ func GetReceipt(db ethdb.Database, txHash common.Hash) *types.Receipt {
if len(data) == 0 {
return nil
}
-
- var receipt types.Receipt
+ var receipt types.ReceiptForStorage
err := rlp.DecodeBytes(data, &receipt)
if err != nil {
glog.V(logger.Core).Infoln("GetReceipt err:", err)
}
- return &receipt
+ return (*types.Receipt)(&receipt)
}
// GetBlockReceipts returns the receipts generated by the transactions
@@ -150,11 +139,14 @@ func GetBlockReceipts(db ethdb.Database, hash common.Hash) types.Receipts {
if len(data) == 0 {
return nil
}
-
- var receipts types.Receipts
- err := rlp.DecodeBytes(data, &receipts)
- if err != nil {
- glog.V(logger.Core).Infoln("GetReceiptse err", err)
+ rs := []*types.ReceiptForStorage{}
+ if err := rlp.DecodeBytes(data, &rs); err != nil {
+ glog.V(logger.Error).Infof("invalid receipt array RLP for hash %x: %v", hash, err)
+ return nil
+ }
+ receipts := make(types.Receipts, len(rs))
+ for i, receipt := range rs {
+ receipts[i] = (*types.Receipt)(receipt)
}
return receipts
}
@@ -162,7 +154,7 @@ func GetBlockReceipts(db ethdb.Database, hash common.Hash) types.Receipts {
// PutBlockReceipts stores the block's transactions associated receipts
// and stores them by block hash in a single slice. This is required for
// forks and chain reorgs
-func PutBlockReceipts(db ethdb.Database, block *types.Block, receipts types.Receipts) error {
+func PutBlockReceipts(db ethdb.Database, hash common.Hash, receipts types.Receipts) error {
rs := make([]*types.ReceiptForStorage, len(receipts))
for i, receipt := range receipts {
rs[i] = (*types.ReceiptForStorage)(receipt)
@@ -171,12 +163,9 @@ func PutBlockReceipts(db ethdb.Database, block *types.Block, receipts types.Rece
if err != nil {
return err
}
-
- hash := block.Hash()
err = db.Put(append(blockReceiptsPre, hash[:]...), bytes)
if err != nil {
return err
}
-
return nil
}
diff --git a/core/types/block.go b/core/types/block.go
index 7a84045a6..1d1cfa515 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -128,7 +128,6 @@ type Block struct {
header *Header
uncles []*Header
transactions Transactions
- receipts Receipts
// caches
hash atomic.Value
@@ -172,8 +171,8 @@ type storageblock struct {
}
var (
- emptyRootHash = DeriveSha(Transactions{})
- emptyUncleHash = CalcUncleHash(nil)
+ EmptyRootHash = DeriveSha(Transactions{})
+ EmptyUncleHash = CalcUncleHash(nil)
)
// NewBlock creates a new block. The input data is copied,
@@ -184,11 +183,11 @@ var (
// are ignored and set to values derived from the given txs, uncles
// and receipts.
func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt) *Block {
- b := &Block{header: copyHeader(header), td: new(big.Int)}
+ b := &Block{header: CopyHeader(header), td: new(big.Int)}
// TODO: panic if len(txs) != len(receipts)
if len(txs) == 0 {
- b.header.TxHash = emptyRootHash
+ b.header.TxHash = EmptyRootHash
} else {
b.header.TxHash = DeriveSha(Transactions(txs))
b.transactions = make(Transactions, len(txs))
@@ -196,21 +195,19 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
}
if len(receipts) == 0 {
- b.header.ReceiptHash = emptyRootHash
+ b.header.ReceiptHash = EmptyRootHash
} else {
b.header.ReceiptHash = DeriveSha(Receipts(receipts))
b.header.Bloom = CreateBloom(receipts)
- b.receipts = make([]*Receipt, len(receipts))
- copy(b.receipts, receipts)
}
if len(uncles) == 0 {
- b.header.UncleHash = emptyUncleHash
+ b.header.UncleHash = EmptyUncleHash
} else {
b.header.UncleHash = CalcUncleHash(uncles)
b.uncles = make([]*Header, len(uncles))
for i := range uncles {
- b.uncles[i] = copyHeader(uncles[i])
+ b.uncles[i] = CopyHeader(uncles[i])
}
}
@@ -221,10 +218,12 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
// header data is copied, changes to header and to the field values
// will not affect the block.
func NewBlockWithHeader(header *Header) *Block {
- return &Block{header: copyHeader(header)}
+ return &Block{header: CopyHeader(header)}
}
-func copyHeader(h *Header) *Header {
+// CopyHeader creates a deep copy of a block header to prevent side effects from
+// modifying a header variable.
+func CopyHeader(h *Header) *Header {
cpy := *h
if cpy.Time = new(big.Int); h.Time != nil {
cpy.Time.Set(h.Time)
@@ -297,7 +296,6 @@ func (b *StorageBlock) DecodeRLP(s *rlp.Stream) error {
// TODO: copies
func (b *Block) Uncles() []*Header { return b.uncles }
func (b *Block) Transactions() Transactions { return b.transactions }
-func (b *Block) Receipts() Receipts { return b.receipts }
func (b *Block) Transaction(hash common.Hash) *Transaction {
for _, transaction := range b.transactions {
@@ -326,7 +324,7 @@ func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash }
func (b *Block) UncleHash() common.Hash { return b.header.UncleHash }
func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Extra) }
-func (b *Block) Header() *Header { return copyHeader(b.header) }
+func (b *Block) Header() *Header { return CopyHeader(b.header) }
func (b *Block) HashNoNonce() common.Hash {
return b.header.HashNoNonce()
@@ -362,7 +360,6 @@ func (b *Block) WithMiningResult(nonce uint64, mixDigest common.Hash) *Block {
return &Block{
header: &cpy,
transactions: b.transactions,
- receipts: b.receipts,
uncles: b.uncles,
}
}
@@ -370,13 +367,13 @@ func (b *Block) WithMiningResult(nonce uint64, mixDigest common.Hash) *Block {
// WithBody returns a new block with the given transaction and uncle contents.
func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block {
block := &Block{
- header: copyHeader(b.header),
+ header: CopyHeader(b.header),
transactions: make([]*Transaction, len(transactions)),
uncles: make([]*Header, len(uncles)),
}
copy(block.transactions, transactions)
for i := range uncles {
- block.uncles[i] = copyHeader(uncles[i])
+ block.uncles[i] = CopyHeader(uncles[i])
}
return block
}
diff --git a/core/types/bloom9.go b/core/types/bloom9.go
index 0629b31d4..cd90fd971 100644
--- a/core/types/bloom9.go
+++ b/core/types/bloom9.go
@@ -17,10 +17,11 @@
package types
import (
+ "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
)
@@ -28,16 +29,56 @@ type bytesBacked interface {
Bytes() []byte
}
+const bloomLength = 256
+
+type Bloom [bloomLength]byte
+
+func BytesToBloom(b []byte) Bloom {
+ var bloom Bloom
+ bloom.SetBytes(b)
+ return bloom
+}
+
+func (b *Bloom) SetBytes(d []byte) {
+ if len(b) < len(d) {
+ panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d)))
+ }
+
+ copy(b[bloomLength-len(d):], d)
+}
+
+func (b *Bloom) Add(d *big.Int) {
+ bin := new(big.Int).SetBytes(b[:])
+ bin.Or(bin, bloom9(d.Bytes()))
+ b.SetBytes(bin.Bytes())
+}
+
+func (b Bloom) Big() *big.Int {
+ return common.Bytes2Big(b[:])
+}
+
+func (b Bloom) Bytes() []byte {
+ return b[:]
+}
+
+func (b Bloom) Test(test *big.Int) bool {
+ return BloomLookup(b, test)
+}
+
+func (b Bloom) TestBytes(test []byte) bool {
+ return b.Test(common.BytesToBig(test))
+}
+
func CreateBloom(receipts Receipts) Bloom {
bin := new(big.Int)
for _, receipt := range receipts {
- bin.Or(bin, LogsBloom(receipt.logs))
+ bin.Or(bin, LogsBloom(receipt.Logs))
}
return BytesToBloom(bin.Bytes())
}
-func LogsBloom(logs state.Logs) *big.Int {
+func LogsBloom(logs vm.Logs) *big.Int {
bin := new(big.Int)
for _, log := range logs {
data := make([]common.Hash, len(log.Topics))
diff --git a/core/types/bloom9_test.go b/core/types/bloom9_test.go
index f020670b1..5744bec6c 100644
--- a/core/types/bloom9_test.go
+++ b/core/types/bloom9_test.go
@@ -16,6 +16,40 @@
package types
+import (
+ "math/big"
+ "testing"
+)
+
+func TestBloom(t *testing.T) {
+ positive := []string{
+ "testtest",
+ "test",
+ "hallo",
+ "other",
+ }
+ negative := []string{
+ "tes",
+ "lo",
+ }
+
+ var bloom Bloom
+ for _, data := range positive {
+ bloom.Add(new(big.Int).SetBytes([]byte(data)))
+ }
+
+ for _, data := range positive {
+ if !bloom.Test(new(big.Int).SetBytes([]byte(data))) {
+ t.Error("expected", data, "to test true")
+ }
+ }
+ for _, data := range negative {
+ if bloom.Test(new(big.Int).SetBytes([]byte(data))) {
+ t.Error("did not expect", data, "to test true")
+ }
+ }
+}
+
/*
import (
"testing"
diff --git a/core/types/common.go b/core/types/common.go
index de6efcd86..fe682f98a 100644
--- a/core/types/common.go
+++ b/core/types/common.go
@@ -16,41 +16,10 @@
package types
-import (
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
-
- "fmt"
-)
+import "github.com/ethereum/go-ethereum/core/vm"
type BlockProcessor interface {
- Process(*Block) (state.Logs, Receipts, error)
-}
-
-const bloomLength = 256
-
-type Bloom [bloomLength]byte
-
-func BytesToBloom(b []byte) Bloom {
- var bloom Bloom
- bloom.SetBytes(b)
- return bloom
-}
-
-func (b *Bloom) SetBytes(d []byte) {
- if len(b) < len(d) {
- panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d)))
- }
-
- copy(b[bloomLength-len(d):], d)
-}
-
-func (b Bloom) Big() *big.Int {
- return common.Bytes2Big(b[:])
-}
-
-func (b Bloom) Bytes() []byte {
- return b[:]
+ Process(*Block) (vm.Logs, Receipts, error)
+ ValidateHeader(*Header, bool, bool) error
+ ValidateHeaderWithParent(*Header, *Header, bool, bool) error
}
diff --git a/core/types/derive_sha.go b/core/types/derive_sha.go
index 478edb0e8..00c42c5bc 100644
--- a/core/types/derive_sha.go
+++ b/core/types/derive_sha.go
@@ -17,8 +17,9 @@
package types
import (
+ "bytes"
+
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
@@ -29,12 +30,12 @@ type DerivableList interface {
}
func DeriveSha(list DerivableList) common.Hash {
- db, _ := ethdb.NewMemDatabase()
- trie := trie.New(nil, db)
+ keybuf := new(bytes.Buffer)
+ trie := new(trie.Trie)
for i := 0; i < list.Len(); i++ {
- key, _ := rlp.EncodeToBytes(uint(i))
- trie.Update(key, list.GetRlp(i))
+ keybuf.Reset()
+ rlp.Encode(keybuf, uint(i))
+ trie.Update(keybuf.Bytes(), list.GetRlp(i))
}
-
- return common.BytesToHash(trie.Root())
+ return trie.Hash()
}
diff --git a/core/types/receipt.go b/core/types/receipt.go
index e01d69005..e7d5203a3 100644
--- a/core/types/receipt.go
+++ b/core/types/receipt.go
@@ -17,99 +17,125 @@
package types
import (
- "bytes"
"fmt"
"io"
"math/big"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/rlp"
)
+// Receipt represents the results of a transaction.
type Receipt struct {
+ // Consensus fields
PostState []byte
CumulativeGasUsed *big.Int
Bloom Bloom
- TxHash common.Hash
- ContractAddress common.Address
- logs state.Logs
- GasUsed *big.Int
-}
-
-func NewReceipt(root []byte, cumalativeGasUsed *big.Int) *Receipt {
- return &Receipt{PostState: common.CopyBytes(root), CumulativeGasUsed: new(big.Int).Set(cumalativeGasUsed)}
-}
+ Logs vm.Logs
-func (self *Receipt) SetLogs(logs state.Logs) {
- self.logs = logs
+ // Implementation fields
+ TxHash common.Hash
+ ContractAddress common.Address
+ GasUsed *big.Int
}
-func (self *Receipt) Logs() state.Logs {
- return self.logs
+// NewReceipt creates a barebone transaction receipt, copying the init fields.
+func NewReceipt(root []byte, cumulativeGasUsed *big.Int) *Receipt {
+ return &Receipt{PostState: common.CopyBytes(root), CumulativeGasUsed: new(big.Int).Set(cumulativeGasUsed)}
}
-func (self *Receipt) EncodeRLP(w io.Writer) error {
- return rlp.Encode(w, []interface{}{self.PostState, self.CumulativeGasUsed, self.Bloom, self.logs})
+// EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt
+// into an RLP stream.
+func (r *Receipt) EncodeRLP(w io.Writer) error {
+ return rlp.Encode(w, []interface{}{r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs})
}
-func (self *Receipt) DecodeRLP(s *rlp.Stream) error {
- var r struct {
+// DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt
+// from an RLP stream.
+func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
+ var receipt struct {
PostState []byte
CumulativeGasUsed *big.Int
Bloom Bloom
- TxHash common.Hash
- ContractAddress common.Address
- Logs state.Logs
- GasUsed *big.Int
+ Logs vm.Logs
}
- if err := s.Decode(&r); err != nil {
+ if err := s.Decode(&receipt); err != nil {
return err
}
- self.PostState, self.CumulativeGasUsed, self.Bloom, self.TxHash, self.ContractAddress, self.logs, self.GasUsed = r.PostState, r.CumulativeGasUsed, r.Bloom, r.TxHash, r.ContractAddress, r.Logs, r.GasUsed
-
+ r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs = receipt.PostState, receipt.CumulativeGasUsed, receipt.Bloom, receipt.Logs
return nil
}
-type ReceiptForStorage Receipt
-
-func (self *ReceiptForStorage) EncodeRLP(w io.Writer) error {
- storageLogs := make([]*state.LogForStorage, len(self.logs))
- for i, log := range self.logs {
- storageLogs[i] = (*state.LogForStorage)(log)
- }
- return rlp.Encode(w, []interface{}{self.PostState, self.CumulativeGasUsed, self.Bloom, self.TxHash, self.ContractAddress, storageLogs, self.GasUsed})
-}
-
-func (self *Receipt) RlpEncode() []byte {
- bytes, err := rlp.EncodeToBytes(self)
+// RlpEncode implements common.RlpEncode required for SHA3 derivation.
+func (r *Receipt) RlpEncode() []byte {
+ bytes, err := rlp.EncodeToBytes(r)
if err != nil {
- fmt.Println("TMP -- RECEIPT ENCODE ERROR", err)
+ panic(err)
}
return bytes
}
-func (self *Receipt) Cmp(other *Receipt) bool {
- if bytes.Compare(self.PostState, other.PostState) != 0 {
- return false
- }
+// String implements the Stringer interface.
+func (r *Receipt) String() string {
+ 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
- return true
+// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
+// into an RLP stream.
+func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
+ logs := make([]*vm.LogForStorage, len(r.Logs))
+ for i, log := range r.Logs {
+ logs[i] = (*vm.LogForStorage)(log)
+ }
+ return rlp.Encode(w, []interface{}{r.PostState, r.CumulativeGasUsed, r.Bloom, r.TxHash, r.ContractAddress, logs, r.GasUsed})
}
-func (self *Receipt) String() string {
- return fmt.Sprintf("receipt{med=%x cgas=%v bloom=%x logs=%v}", self.PostState, self.CumulativeGasUsed, self.Bloom, self.logs)
+// DecodeRLP implements rlp.Decoder, and loads both consensus and implementation
+// fields of a receipt from an RLP stream.
+func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
+ var receipt struct {
+ PostState []byte
+ CumulativeGasUsed *big.Int
+ Bloom Bloom
+ TxHash common.Hash
+ ContractAddress common.Address
+ Logs []*vm.LogForStorage
+ GasUsed *big.Int
+ }
+ if err := s.Decode(&receipt); err != nil {
+ return err
+ }
+ // Assign the consensus fields
+ r.PostState, r.CumulativeGasUsed, r.Bloom = receipt.PostState, receipt.CumulativeGasUsed, receipt.Bloom
+ r.Logs = make(vm.Logs, len(receipt.Logs))
+ for i, log := range receipt.Logs {
+ r.Logs[i] = (*vm.Log)(log)
+ }
+ // Assign the implementation fields
+ r.TxHash, r.ContractAddress, r.GasUsed = receipt.TxHash, receipt.ContractAddress, receipt.GasUsed
+
+ return nil
}
+// Receipts is a wrapper around a Receipt array to implement types.DerivableList.
type Receipts []*Receipt
-func (self Receipts) RlpEncode() []byte {
- bytes, err := rlp.EncodeToBytes(self)
+// RlpEncode implements common.RlpEncode required for SHA3 derivation.
+func (r Receipts) RlpEncode() []byte {
+ bytes, err := rlp.EncodeToBytes(r)
if err != nil {
- fmt.Println("TMP -- RECEIPTS ENCODE ERROR", err)
+ panic(err)
}
return bytes
}
-func (self Receipts) Len() int { return len(self) }
-func (self Receipts) GetRlp(i int) []byte { return common.Rlp(self[i]) }
+// Len returns the number of receipts in this list.
+func (r Receipts) Len() int { return len(r) }
+
+// GetRlp returns the RLP encoding of one receipt from the list.
+func (r Receipts) GetRlp(i int) []byte { return common.Rlp(r[i]) }
diff --git a/core/vm/asm.go b/core/vm/asm.go
index 639201e50..065d3eb97 100644
--- a/core/vm/asm.go
+++ b/core/vm/asm.go
@@ -23,6 +23,8 @@ import (
"github.com/ethereum/go-ethereum/common"
)
+// Dissassemble dissassembles the byte code and returns the string
+// representation (human readable opcodes).
func Disassemble(script []byte) (asm []string) {
pc := new(big.Int)
for {
diff --git a/core/vm/common.go b/core/vm/common.go
index 2e03ec80b..2d1aa9332 100644
--- a/core/vm/common.go
+++ b/core/vm/common.go
@@ -22,34 +22,34 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger/glog"
+ "github.com/ethereum/go-ethereum/params"
)
// Global Debug flag indicating Debug VM (full logging)
var Debug bool
+// Type is the VM type accepted by **NewVm**
type Type byte
const (
- StdVmTy Type = iota
- JitVmTy
+ StdVmTy Type = iota // Default standard VM
+ JitVmTy // LLVM JIT VM
MaxVmTy
-
- LogTyPretty byte = 0x1
- LogTyDiff byte = 0x2
)
var (
- Pow256 = common.BigPow(2, 256)
+ Pow256 = common.BigPow(2, 256) // Pow256 is 2**256
- U256 = common.U256
- S256 = common.S256
+ U256 = common.U256 // Shortcut to common.U256
+ S256 = common.S256 // Shortcut to common.S256
- Zero = common.Big0
- One = common.Big1
+ Zero = common.Big0 // Shortcut to common.Big0
+ One = common.Big1 // Shortcut to common.Big1
- max = big.NewInt(math.MaxInt64)
+ max = big.NewInt(math.MaxInt64) // Maximum 64 bit integer
)
+// NewVm returns a new VM based on the Environment
func NewVm(env Environment) VirtualMachine {
switch env.VmType() {
case JitVmTy:
@@ -62,6 +62,7 @@ func NewVm(env Environment) VirtualMachine {
}
}
+// calculates the memory size required for a step
func calcMemSize(off, l *big.Int) *big.Int {
if l.Cmp(common.Big0) == 0 {
return common.Big0
@@ -70,6 +71,32 @@ func calcMemSize(off, l *big.Int) *big.Int {
return new(big.Int).Add(off, l)
}
+// calculates the quadratic gas
+func quadMemGas(mem *Memory, newMemSize, gas *big.Int) {
+ if newMemSize.Cmp(common.Big0) > 0 {
+ newMemSizeWords := toWordSize(newMemSize)
+ newMemSize.Mul(newMemSizeWords, u256(32))
+
+ if newMemSize.Cmp(u256(int64(mem.Len()))) > 0 {
+ // be careful reusing variables here when changing.
+ // The order has been optimised to reduce allocation
+ oldSize := toWordSize(big.NewInt(int64(mem.Len())))
+ pow := new(big.Int).Exp(oldSize, common.Big2, Zero)
+ linCoef := oldSize.Mul(oldSize, params.MemoryGas)
+ quadCoef := new(big.Int).Div(pow, params.QuadCoeffDiv)
+ oldTotalFee := new(big.Int).Add(linCoef, quadCoef)
+
+ pow.Exp(newMemSizeWords, common.Big2, Zero)
+ linCoef = linCoef.Mul(newMemSizeWords, params.MemoryGas)
+ quadCoef = quadCoef.Div(pow, params.QuadCoeffDiv)
+ newTotalFee := linCoef.Add(linCoef, quadCoef)
+
+ fee := newTotalFee.Sub(newTotalFee, oldTotalFee)
+ gas.Add(gas, fee)
+ }
+ }
+}
+
// Simple helper
func u256(n int64) *big.Int {
return big.NewInt(n)
@@ -86,6 +113,8 @@ func toValue(val *big.Int) interface{} {
return val
}
+// getData returns a slice from the data based on the start and size and pads
+// up to size with zero's. This function is overflow safe.
func getData(data []byte, start, size *big.Int) []byte {
dlen := big.NewInt(int64(len(data)))
@@ -94,7 +123,9 @@ func getData(data []byte, start, size *big.Int) []byte {
return common.RightPadBytes(data[s.Uint64():e.Uint64()], int(size.Uint64()))
}
-func UseGas(gas, amount *big.Int) bool {
+// useGas attempts to subtract the amount of gas and returns whether it was
+// successful
+func useGas(gas, amount *big.Int) bool {
if gas.Cmp(amount) < 0 {
return false
}
diff --git a/core/vm/context.go b/core/vm/contract.go
index d17934ba5..95417e747 100644
--- a/core/vm/context.go
+++ b/core/vm/contract.go
@@ -22,15 +22,18 @@ import (
"github.com/ethereum/go-ethereum/common"
)
-type ContextRef interface {
+// ContractRef is a reference to the contract's backing object
+type ContractRef interface {
ReturnGas(*big.Int, *big.Int)
Address() common.Address
SetCode([]byte)
}
-type Context struct {
- caller ContextRef
- self ContextRef
+// Contract represents an ethereum contract in the state database. It contains
+// the the contract code, calling arguments. Contract implements ContractReg
+type Contract struct {
+ caller ContractRef
+ self ContractRef
jumpdests destinations // result of JUMPDEST analysis.
@@ -44,10 +47,10 @@ type Context struct {
}
// Create a new context for the given data items.
-func NewContext(caller ContextRef, object ContextRef, value, gas, price *big.Int) *Context {
- c := &Context{caller: caller, self: object, Args: nil}
+func NewContract(caller ContractRef, object ContractRef, value, gas, price *big.Int) *Contract {
+ c := &Contract{caller: caller, self: object, Args: nil}
- if parent, ok := caller.(*Context); ok {
+ if parent, ok := caller.(*Contract); ok {
// Reuse JUMPDEST analysis from parent context if available.
c.jumpdests = parent.jumpdests
} else {
@@ -66,11 +69,13 @@ func NewContext(caller ContextRef, object ContextRef, value, gas, price *big.Int
return c
}
-func (c *Context) GetOp(n uint64) OpCode {
+// GetOp returns the n'th element in the contract's byte array
+func (c *Contract) GetOp(n uint64) OpCode {
return OpCode(c.GetByte(n))
}
-func (c *Context) GetByte(n uint64) byte {
+// GetByte returns the n'th byte in the contract's byte array
+func (c *Contract) GetByte(n uint64) byte {
if n < uint64(len(c.Code)) {
return c.Code[n]
}
@@ -78,43 +83,44 @@ func (c *Context) GetByte(n uint64) byte {
return 0
}
-func (c *Context) Return(ret []byte) []byte {
+// Return returns the given ret argument and returns any remaining gas to the
+// caller
+func (c *Contract) Return(ret []byte) []byte {
// Return the remaining gas to the caller
c.caller.ReturnGas(c.Gas, c.Price)
return ret
}
-/*
- * Gas functions
- */
-func (c *Context) UseGas(gas *big.Int) (ok bool) {
- ok = UseGas(c.Gas, gas)
+// UseGas attempts the use gas and subtracts it and returns true on success
+func (c *Contract) UseGas(gas *big.Int) (ok bool) {
+ ok = useGas(c.Gas, gas)
if ok {
c.UsedGas.Add(c.UsedGas, gas)
}
return
}
-// Implement the caller interface
-func (c *Context) ReturnGas(gas, price *big.Int) {
+// ReturnGas adds the given gas back to itself.
+func (c *Contract) ReturnGas(gas, price *big.Int) {
// Return the gas to the context
c.Gas.Add(c.Gas, gas)
c.UsedGas.Sub(c.UsedGas, gas)
}
-/*
- * Set / Get
- */
-func (c *Context) Address() common.Address {
+// Address returns the contracts address
+func (c *Contract) Address() common.Address {
return c.self.Address()
}
-func (self *Context) SetCode(code []byte) {
+// SetCode sets the code to the contract
+func (self *Contract) SetCode(code []byte) {
self.Code = code
}
-func (self *Context) SetCallCode(addr *common.Address, code []byte) {
+// SetCallCode sets the code of the contract and address of the backing data
+// object
+func (self *Contract) SetCallCode(addr *common.Address, code []byte) {
self.Code = code
self.CodeAddr = addr
}
diff --git a/core/vm/contracts.go b/core/vm/contracts.go
index b965fa095..22cb9eab2 100644
--- a/core/vm/contracts.go
+++ b/core/vm/contracts.go
@@ -26,22 +26,22 @@ import (
"github.com/ethereum/go-ethereum/params"
)
-type Address interface {
- Call(in []byte) []byte
-}
-
+// PrecompiledAccount represents a native ethereum contract
type PrecompiledAccount struct {
Gas func(l int) *big.Int
fn func(in []byte) []byte
}
+// Call calls the native function
func (self PrecompiledAccount) Call(in []byte) []byte {
return self.fn(in)
}
+// Precompiled contains the default set of ethereum contracts
var Precompiled = PrecompiledContracts()
-// XXX Could set directly. Testing requires resetting and setting of pre compiled contracts.
+// PrecompiledContracts returns the default set of precompiled ethereum
+// contracts defined by the ethereum yellow paper.
func PrecompiledContracts() map[string]*PrecompiledAccount {
return map[string]*PrecompiledAccount{
// ECRECOVER
diff --git a/core/vm/doc.go b/core/vm/doc.go
new file mode 100644
index 000000000..debbdb35e
--- /dev/null
+++ b/core/vm/doc.go
@@ -0,0 +1,35 @@
+// Copyright 2014 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 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 seperate 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.
+*/
+package vm
diff --git a/core/vm/environment.go b/core/vm/environment.go
index 916081f51..299d12674 100644
--- a/core/vm/environment.go
+++ b/core/vm/environment.go
@@ -17,39 +17,86 @@
package vm
import (
- "errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
)
// Environment is is required by the virtual machine to get information from
-// it's own isolated environment. For an example see `core.VMEnv`
-type Environment interface {
- State() *state.StateDB
+// it's own isolated environment.
+// Environment is an EVM requirement and helper which allows access to outside
+// information such as states.
+type Environment interface {
+ // The state database
+ Db() Database
+ // Creates a restorable snapshot
+ MakeSnapshot() Database
+ // Set database to previous snapshot
+ SetSnapshot(Database)
+ // Address of the original invoker (first occurance of the VM invoker)
Origin() common.Address
+ // The block number this VM is invoken on
BlockNumber() *big.Int
- GetHash(n uint64) common.Hash
+ // The n'th hash ago from this block number
+ GetHash(uint64) common.Hash
+ // The handler's address
Coinbase() common.Address
+ // The current time (block time)
Time() *big.Int
+ // Difficulty set on the current block
Difficulty() *big.Int
+ // The gas limit of the block
GasLimit() *big.Int
- CanTransfer(from Account, balance *big.Int) bool
- Transfer(from, to Account, amount *big.Int) error
- AddLog(*state.Log)
+ // Determines whether it's possible to transact
+ CanTransfer(from common.Address, balance *big.Int) bool
+ // Transfers amount from one account to the other
+ Transfer(from, to Account, amount *big.Int)
+ // Adds a LOG to the state
+ AddLog(*Log)
+ // Adds a structured log to the env
AddStructLog(StructLog)
+ // Returns all coalesced structured logs
StructLogs() []StructLog
+ // Type of the VM
VmType() Type
+ // Current calling depth
Depth() int
SetDepth(i int)
- Call(me ContextRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error)
- CallCode(me ContextRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error)
- Create(me ContextRef, data []byte, gas, price, value *big.Int) ([]byte, error, ContextRef)
+ // Call another contract
+ Call(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error)
+ // Take another's contract code and execute within our own context
+ CallCode(me ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error)
+ // Create a new contract
+ Create(me ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error)
+}
+
+// Database is a EVM database for full state querying
+type Database interface {
+ GetAccount(common.Address) Account
+ CreateAccount(common.Address) Account
+
+ AddBalance(common.Address, *big.Int)
+ GetBalance(common.Address) *big.Int
+
+ GetNonce(common.Address) uint64
+ SetNonce(common.Address, uint64)
+
+ GetCode(common.Address) []byte
+ SetCode(common.Address, []byte)
+
+ AddRefund(*big.Int)
+ GetRefund() *big.Int
+
+ GetState(common.Address, common.Hash) common.Hash
+ SetState(common.Address, common.Hash, common.Hash)
+
+ Delete(common.Address) bool
+ Exist(common.Address) bool
+ IsDeleted(common.Address) bool
}
// StructLog is emited to the Environment each cycle and lists information about the curent internal state
@@ -68,18 +115,10 @@ type StructLog struct {
type Account interface {
SubBalance(amount *big.Int)
AddBalance(amount *big.Int)
+ SetBalance(*big.Int)
+ SetNonce(uint64)
Balance() *big.Int
Address() common.Address
-}
-
-// generic transfer method
-func Transfer(from, to Account, amount *big.Int) error {
- if from.Balance().Cmp(amount) < 0 {
- return errors.New("Insufficient balance in account")
- }
-
- from.SubBalance(amount)
- to.AddBalance(amount)
-
- return nil
+ ReturnGas(*big.Int, *big.Int)
+ SetCode([]byte)
}
diff --git a/core/vm/gas.go b/core/vm/gas.go
index b2f068e6e..bff0ac91b 100644
--- a/core/vm/gas.go
+++ b/core/vm/gas.go
@@ -37,6 +37,7 @@ var (
GasContractByte = big.NewInt(200)
)
+// baseCheck checks for any stack error underflows
func baseCheck(op OpCode, stack *stack, gas *big.Int) error {
// PUSH and DUP are a bit special. They all cost the same but we do want to have checking on stack push limit
// PUSH is also allowed to calculate the same price for all PUSHes
@@ -63,6 +64,7 @@ func baseCheck(op OpCode, stack *stack, gas *big.Int) error {
return nil
}
+// casts a arbitrary number to the amount of words (sets of 32 bytes)
func toWordSize(size *big.Int) *big.Int {
tmp := new(big.Int)
tmp.Add(size, u256(31))
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index aa0117cc8..2e868521e 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -17,49 +17,123 @@
package vm
import (
+ "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)
-type instrFn func(instr instruction, env Environment, context *Context, memory *Memory, stack *stack)
-type instrExFn func(instr instruction, ret *big.Int, env Environment, context *Context, memory *Memory, stack *stack)
+type programInstruction interface {
+ // executes the program instruction and allows the instruction to modify the state of the program
+ do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error)
+ // returns whether the program instruction halts the execution of the JIT
+ halts() bool
+ // Returns the current op code (debugging purposes)
+ Op() OpCode
+}
+
+type instrFn func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack)
type instruction struct {
- op OpCode
- pc uint64
- fn instrFn
- specFn instrExFn
- data *big.Int
+ op OpCode
+ pc uint64
+ fn instrFn
+ data *big.Int
gas *big.Int
spop int
spush int
+
+ returns bool
+}
+
+func jump(mapping map[uint64]uint64, destinations map[uint64]struct{}, contract *Contract, to *big.Int) (uint64, error) {
+ if !validDest(destinations, to) {
+ nop := contract.GetOp(to.Uint64())
+ return 0, fmt.Errorf("invalid jump destination (%v) %v", nop, to)
+ }
+
+ return mapping[to.Uint64()], nil
+}
+
+func (instr instruction) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error) {
+ // calculate the new memory size and gas price for the current executing opcode
+ newMemSize, cost, err := jitCalculateGasAndSize(env, contract, instr, env.Db(), memory, stack)
+ if err != nil {
+ return nil, err
+ }
+
+ // Use the calculated gas. When insufficient gas is present, use all gas and return an
+ // Out Of Gas error
+ if !contract.UseGas(cost) {
+ return nil, OutOfGasError
+ }
+ // Resize the memory calculated previously
+ memory.Resize(newMemSize.Uint64())
+
+ // These opcodes return an argument and are therefor handled
+ // differently from the rest of the opcodes
+ switch instr.op {
+ case JUMP:
+ if pos, err := jump(program.mapping, program.destinations, contract, stack.pop()); err != nil {
+ return nil, err
+ } else {
+ *pc = pos
+ return nil, nil
+ }
+ case JUMPI:
+ pos, cond := stack.pop(), stack.pop()
+ if cond.Cmp(common.BigTrue) >= 0 {
+ if pos, err := jump(program.mapping, program.destinations, contract, pos); err != nil {
+ return nil, err
+ } else {
+ *pc = pos
+ return nil, nil
+ }
+ }
+ case RETURN:
+ offset, size := stack.pop(), stack.pop()
+ return memory.GetPtr(offset.Int64(), size.Int64()), nil
+ default:
+ if instr.fn == nil {
+ return nil, fmt.Errorf("Invalid opcode 0x%x", instr.op)
+ }
+ instr.fn(instr, pc, env, contract, memory, stack)
+ }
+ *pc++
+ return nil, nil
+}
+
+func (instr instruction) halts() bool {
+ return instr.returns
+}
+
+func (instr instruction) Op() OpCode {
+ return instr.op
}
-func opStaticJump(instr instruction, ret *big.Int, env Environment, context *Context, memory *Memory, stack *stack) {
+func opStaticJump(instr instruction, pc *uint64, ret *big.Int, env Environment, contract *Contract, memory *Memory, stack *stack) {
ret.Set(instr.data)
}
-func opAdd(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opAdd(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
stack.push(U256(x.Add(x, y)))
}
-func opSub(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opSub(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
stack.push(U256(x.Sub(x, y)))
}
-func opMul(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opMul(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
stack.push(U256(x.Mul(x, y)))
}
-func opDiv(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opDiv(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
if y.Cmp(common.Big0) != 0 {
stack.push(U256(x.Div(x, y)))
@@ -68,7 +142,7 @@ func opDiv(instr instruction, env Environment, context *Context, memory *Memory,
}
}
-func opSdiv(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opSdiv(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := S256(stack.pop()), S256(stack.pop())
if y.Cmp(common.Big0) == 0 {
stack.push(new(big.Int))
@@ -88,7 +162,7 @@ func opSdiv(instr instruction, env Environment, context *Context, memory *Memory
}
}
-func opMod(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opMod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
if y.Cmp(common.Big0) == 0 {
stack.push(new(big.Int))
@@ -97,7 +171,7 @@ func opMod(instr instruction, env Environment, context *Context, memory *Memory,
}
}
-func opSmod(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opSmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := S256(stack.pop()), S256(stack.pop())
if y.Cmp(common.Big0) == 0 {
@@ -117,12 +191,12 @@ func opSmod(instr instruction, env Environment, context *Context, memory *Memory
}
}
-func opExp(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opExp(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
stack.push(U256(x.Exp(x, y, Pow256)))
}
-func opSignExtend(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opSignExtend(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
back := stack.pop()
if back.Cmp(big.NewInt(31)) < 0 {
bit := uint(back.Uint64()*8 + 7)
@@ -139,12 +213,12 @@ func opSignExtend(instr instruction, env Environment, context *Context, memory *
}
}
-func opNot(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opNot(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x := stack.pop()
stack.push(U256(x.Not(x)))
}
-func opLt(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opLt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
if x.Cmp(y) < 0 {
stack.push(big.NewInt(1))
@@ -153,7 +227,7 @@ func opLt(instr instruction, env Environment, context *Context, memory *Memory,
}
}
-func opGt(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opGt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
if x.Cmp(y) > 0 {
stack.push(big.NewInt(1))
@@ -162,7 +236,7 @@ func opGt(instr instruction, env Environment, context *Context, memory *Memory,
}
}
-func opSlt(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opSlt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := S256(stack.pop()), S256(stack.pop())
if x.Cmp(S256(y)) < 0 {
stack.push(big.NewInt(1))
@@ -171,7 +245,7 @@ func opSlt(instr instruction, env Environment, context *Context, memory *Memory,
}
}
-func opSgt(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opSgt(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := S256(stack.pop()), S256(stack.pop())
if x.Cmp(y) > 0 {
stack.push(big.NewInt(1))
@@ -180,7 +254,7 @@ func opSgt(instr instruction, env Environment, context *Context, memory *Memory,
}
}
-func opEq(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opEq(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
if x.Cmp(y) == 0 {
stack.push(big.NewInt(1))
@@ -189,7 +263,7 @@ func opEq(instr instruction, env Environment, context *Context, memory *Memory,
}
}
-func opIszero(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opIszero(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x := stack.pop()
if x.Cmp(common.Big0) > 0 {
stack.push(new(big.Int))
@@ -198,19 +272,19 @@ func opIszero(instr instruction, env Environment, context *Context, memory *Memo
}
}
-func opAnd(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opAnd(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
stack.push(x.And(x, y))
}
-func opOr(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opOr(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
stack.push(x.Or(x, y))
}
-func opXor(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opXor(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y := stack.pop(), stack.pop()
stack.push(x.Xor(x, y))
}
-func opByte(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opByte(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
th, val := stack.pop(), stack.pop()
if th.Cmp(big.NewInt(32)) < 0 {
byte := big.NewInt(int64(common.LeftPadBytes(val.Bytes(), 32)[th.Int64()]))
@@ -219,7 +293,7 @@ func opByte(instr instruction, env Environment, context *Context, memory *Memory
stack.push(new(big.Int))
}
}
-func opAddmod(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opAddmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y, z := stack.pop(), stack.pop(), stack.pop()
if z.Cmp(Zero) > 0 {
add := x.Add(x, y)
@@ -229,7 +303,7 @@ func opAddmod(instr instruction, env Environment, context *Context, memory *Memo
stack.push(new(big.Int))
}
}
-func opMulmod(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opMulmod(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
x, y, z := stack.pop(), stack.pop(), stack.pop()
if z.Cmp(Zero) > 0 {
mul := x.Mul(x, y)
@@ -240,92 +314,92 @@ func opMulmod(instr instruction, env Environment, context *Context, memory *Memo
}
}
-func opSha3(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opSha3(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
offset, size := stack.pop(), stack.pop()
hash := crypto.Sha3(memory.Get(offset.Int64(), size.Int64()))
stack.push(common.BytesToBig(hash))
}
-func opAddress(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
- stack.push(common.Bytes2Big(context.Address().Bytes()))
+func opAddress(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ stack.push(common.Bytes2Big(contract.Address().Bytes()))
}
-func opBalance(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opBalance(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
addr := common.BigToAddress(stack.pop())
- balance := env.State().GetBalance(addr)
+ balance := env.Db().GetBalance(addr)
stack.push(new(big.Int).Set(balance))
}
-func opOrigin(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opOrigin(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.push(env.Origin().Big())
}
-func opCaller(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
- stack.push(common.Bytes2Big(context.caller.Address().Bytes()))
+func opCaller(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ stack.push(common.Bytes2Big(contract.caller.Address().Bytes()))
}
-func opCallValue(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
- stack.push(new(big.Int).Set(context.value))
+func opCallValue(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ stack.push(new(big.Int).Set(contract.value))
}
-func opCalldataLoad(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
- stack.push(common.Bytes2Big(getData(context.Input, stack.pop(), common.Big32)))
+func opCalldataLoad(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ stack.push(common.Bytes2Big(getData(contract.Input, stack.pop(), common.Big32)))
}
-func opCalldataSize(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
- stack.push(big.NewInt(int64(len(context.Input))))
+func opCalldataSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ stack.push(big.NewInt(int64(len(contract.Input))))
}
-func opCalldataCopy(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opCalldataCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
var (
mOff = stack.pop()
cOff = stack.pop()
l = stack.pop()
)
- memory.Set(mOff.Uint64(), l.Uint64(), getData(context.Input, cOff, l))
+ memory.Set(mOff.Uint64(), l.Uint64(), getData(contract.Input, cOff, l))
}
-func opExtCodeSize(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opExtCodeSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
addr := common.BigToAddress(stack.pop())
- l := big.NewInt(int64(len(env.State().GetCode(addr))))
+ l := big.NewInt(int64(len(env.Db().GetCode(addr))))
stack.push(l)
}
-func opCodeSize(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
- l := big.NewInt(int64(len(context.Code)))
+func opCodeSize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ l := big.NewInt(int64(len(contract.Code)))
stack.push(l)
}
-func opCodeCopy(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
var (
mOff = stack.pop()
cOff = stack.pop()
l = stack.pop()
)
- codeCopy := getData(context.Code, cOff, l)
+ codeCopy := getData(contract.Code, cOff, l)
memory.Set(mOff.Uint64(), l.Uint64(), codeCopy)
}
-func opExtCodeCopy(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opExtCodeCopy(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
var (
addr = common.BigToAddress(stack.pop())
mOff = stack.pop()
cOff = stack.pop()
l = stack.pop()
)
- codeCopy := getData(env.State().GetCode(addr), cOff, l)
+ codeCopy := getData(env.Db().GetCode(addr), cOff, l)
memory.Set(mOff.Uint64(), l.Uint64(), codeCopy)
}
-func opGasprice(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
- stack.push(new(big.Int).Set(context.Price))
+func opGasprice(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ stack.push(new(big.Int).Set(contract.Price))
}
-func opBlockhash(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opBlockhash(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
num := stack.pop()
n := new(big.Int).Sub(env.BlockNumber(), common.Big257)
@@ -336,43 +410,43 @@ func opBlockhash(instr instruction, env Environment, context *Context, memory *M
}
}
-func opCoinbase(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opCoinbase(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.push(env.Coinbase().Big())
}
-func opTimestamp(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opTimestamp(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.push(U256(new(big.Int).Set(env.Time())))
}
-func opNumber(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opNumber(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.push(U256(new(big.Int).Set(env.BlockNumber())))
}
-func opDifficulty(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opDifficulty(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.push(U256(new(big.Int).Set(env.Difficulty())))
}
-func opGasLimit(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opGasLimit(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.push(U256(new(big.Int).Set(env.GasLimit())))
}
-func opPop(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opPop(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.pop()
}
-func opPush(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opPush(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.push(new(big.Int).Set(instr.data))
}
-func opDup(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opDup(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.dup(int(instr.data.Int64()))
}
-func opSwap(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opSwap(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.swap(int(instr.data.Int64()))
}
-func opLog(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opLog(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
n := int(instr.data.Int64())
topics := make([]common.Hash, n)
mStart, mSize := stack.pop(), stack.pop()
@@ -381,85 +455,88 @@ func opLog(instr instruction, env Environment, context *Context, memory *Memory,
}
d := memory.Get(mStart.Int64(), mSize.Int64())
- log := state.NewLog(context.Address(), topics, d, env.BlockNumber().Uint64())
+ log := NewLog(contract.Address(), topics, d, env.BlockNumber().Uint64())
env.AddLog(log)
}
-func opMload(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opMload(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
offset := stack.pop()
val := common.BigD(memory.Get(offset.Int64(), 32))
stack.push(val)
}
-func opMstore(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opMstore(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
// pop value of the stack
mStart, val := stack.pop(), stack.pop()
memory.Set(mStart.Uint64(), 32, common.BigToBytes(val, 256))
}
-func opMstore8(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opMstore8(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
off, val := stack.pop().Int64(), stack.pop().Int64()
memory.store[off] = byte(val & 0xff)
}
-func opSload(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opSload(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
loc := common.BigToHash(stack.pop())
- val := env.State().GetState(context.Address(), loc).Big()
+ val := env.Db().GetState(contract.Address(), loc).Big()
stack.push(val)
}
-func opSstore(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opSstore(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
loc := common.BigToHash(stack.pop())
val := stack.pop()
- env.State().SetState(context.Address(), loc, common.BigToHash(val))
+ env.Db().SetState(contract.Address(), loc, common.BigToHash(val))
}
-func opJump(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {}
-func opJumpi(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {}
-func opJumpdest(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {}
+func opJump(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+}
+func opJumpi(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+}
+func opJumpdest(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+}
-func opPc(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opPc(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.push(new(big.Int).Set(instr.data))
}
-func opMsize(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opMsize(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
stack.push(big.NewInt(int64(memory.Len())))
}
-func opGas(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
- stack.push(new(big.Int).Set(context.Gas))
+func opGas(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ stack.push(new(big.Int).Set(contract.Gas))
}
-func opCreate(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opCreate(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
var (
value = stack.pop()
offset, size = stack.pop(), stack.pop()
input = memory.Get(offset.Int64(), size.Int64())
- gas = new(big.Int).Set(context.Gas)
+ gas = new(big.Int).Set(contract.Gas)
addr common.Address
+ ret []byte
+ suberr error
)
- context.UseGas(context.Gas)
- ret, suberr, ref := env.Create(context, input, gas, context.Price, value)
+ contract.UseGas(contract.Gas)
+ ret, addr, suberr = env.Create(contract, input, gas, contract.Price, value)
if suberr != nil {
stack.push(new(big.Int))
-
} else {
// gas < len(ret) * Createinstr.dataGas == NO_CODE
dataGas := big.NewInt(int64(len(ret)))
dataGas.Mul(dataGas, params.CreateDataGas)
- if context.UseGas(dataGas) {
- ref.SetCode(ret)
+ if contract.UseGas(dataGas) {
+ env.Db().SetCode(addr, ret)
}
- addr = ref.Address()
stack.push(addr.Big())
}
}
-func opCall(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opCall(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
gas := stack.pop()
// pop gas and value of the stack.
addr, value := stack.pop(), stack.pop()
@@ -478,7 +555,7 @@ func opCall(instr instruction, env Environment, context *Context, memory *Memory
gas.Add(gas, params.CallStipend)
}
- ret, err := env.Call(context, address, args, gas, context.Price, value)
+ ret, err := env.Call(contract, address, args, gas, contract.Price, value)
if err != nil {
stack.push(new(big.Int))
@@ -490,7 +567,7 @@ func opCall(instr instruction, env Environment, context *Context, memory *Memory
}
}
-func opCallCode(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
+func opCallCode(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
gas := stack.pop()
// pop gas and value of the stack.
addr, value := stack.pop(), stack.pop()
@@ -509,7 +586,7 @@ func opCallCode(instr instruction, env Environment, context *Context, memory *Me
gas.Add(gas, params.CallStipend)
}
- ret, err := env.CallCode(context, address, args, gas, context.Price, value)
+ ret, err := env.CallCode(contract, address, args, gas, contract.Price, value)
if err != nil {
stack.push(new(big.Int))
@@ -521,14 +598,56 @@ func opCallCode(instr instruction, env Environment, context *Context, memory *Me
}
}
-func opReturn(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {}
-func opStop(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {}
+func opReturn(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+}
+func opStop(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+}
+
+func opSuicide(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ balance := env.Db().GetBalance(contract.Address())
+ env.Db().AddBalance(common.BigToAddress(stack.pop()), balance)
-func opSuicide(instr instruction, env Environment, context *Context, memory *Memory, stack *stack) {
- receiver := env.State().GetOrNewStateObject(common.BigToAddress(stack.pop()))
- balance := env.State().GetBalance(context.Address())
+ env.Db().Delete(contract.Address())
+}
- receiver.AddBalance(balance)
+// following functions are used by the instruction jump table
+
+// make log instruction function
+func makeLog(size int) instrFn {
+ return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ topics := make([]common.Hash, size)
+ mStart, mSize := stack.pop(), stack.pop()
+ for i := 0; i < size; i++ {
+ topics[i] = common.BigToHash(stack.pop())
+ }
- env.State().Delete(context.Address())
+ d := memory.Get(mStart.Int64(), mSize.Int64())
+ log := NewLog(contract.Address(), topics, d, env.BlockNumber().Uint64())
+ env.AddLog(log)
+ }
+}
+
+// make push instruction function
+func makePush(size uint64, bsize *big.Int) instrFn {
+ return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ byts := getData(contract.Code, new(big.Int).SetUint64(*pc+1), bsize)
+ stack.push(common.Bytes2Big(byts))
+ *pc += size
+ }
+}
+
+// make push instruction function
+func makeDup(size int64) instrFn {
+ return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ stack.dup(int(size))
+ }
+}
+
+// make swap instruction function
+func makeSwap(size int64) instrFn {
+ // switch n + 1 otherwise n would be swapped with n
+ size += 1
+ return func(instr instruction, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) {
+ stack.swap(int(size))
+ }
}
diff --git a/core/vm/jit.go b/core/vm/jit.go
index 084d2a3f3..1aa7d7ef2 100644
--- a/core/vm/jit.go
+++ b/core/vm/jit.go
@@ -20,10 +20,12 @@ import (
"fmt"
"math/big"
"sync/atomic"
+ "time"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/params"
"github.com/hashicorp/golang-lru"
)
@@ -35,6 +37,14 @@ const (
progCompile
progReady
progError
+
+ defaultJitMaxCache int = 64
+)
+
+var (
+ EnableJit bool // Enables the JIT VM
+ ForceJit bool // Force the JIT, skip byte VM
+ MaxProgSize int // Max cache size for JIT Programs
)
var programs *lru.Cache
@@ -74,11 +84,11 @@ type Program struct {
Id common.Hash // Id of the program
status int32 // status should be accessed atomically
- context *Context
+ contract *Contract
- instructions []instruction // instruction set
- mapping map[uint64]int // real PC mapping to array indices
- destinations map[uint64]struct{} // cached jump destinations
+ instructions []programInstruction // instruction set
+ mapping map[uint64]uint64 // real PC mapping to array indices
+ destinations map[uint64]struct{} // cached jump destinations
code []byte
}
@@ -87,7 +97,7 @@ type Program struct {
func NewProgram(code []byte) *Program {
program := &Program{
Id: crypto.Sha3Hash(code),
- mapping: make(map[uint64]int),
+ mapping: make(map[uint64]uint64),
destinations: make(map[uint64]struct{}),
code: code,
}
@@ -108,10 +118,12 @@ func (p *Program) addInstr(op OpCode, pc uint64, fn instrFn, data *big.Int) {
baseOp = DUP1
}
base := _baseCheck[baseOp]
- instr := instruction{op, pc, fn, nil, data, base.gas, base.stackPop, base.stackPush}
+
+ returns := op == RETURN || op == SUICIDE || op == STOP
+ instr := instruction{op, pc, fn, data, base.gas, base.stackPop, base.stackPush, returns}
p.instructions = append(p.instructions, instr)
- p.mapping[pc] = len(p.instructions) - 1
+ p.mapping[pc] = uint64(len(p.instructions) - 1)
}
// CompileProgram compiles the given program and return an error when it fails
@@ -127,6 +139,13 @@ func CompileProgram(program *Program) (err error) {
atomic.StoreInt32(&program.status, int32(progReady))
}
}()
+ if glog.V(logger.Debug) {
+ glog.Infof("compiling %x\n", program.Id[:4])
+ tstart := time.Now()
+ defer func() {
+ glog.Infof("compiled %x instrc: %d time: %v\n", program.Id[:4], len(program.instructions), time.Since(tstart))
+ }()
+ }
// loop thru the opcodes and "compile" in to instructions
for pc := uint64(0); pc < uint64(len(program.code)); pc++ {
@@ -264,101 +283,58 @@ func CompileProgram(program *Program) (err error) {
program.addInstr(op, pc, opReturn, nil)
case SUICIDE:
program.addInstr(op, pc, opSuicide, nil)
- case STOP: // Stop the context
+ case STOP: // Stop the contract
program.addInstr(op, pc, opStop, nil)
default:
program.addInstr(op, pc, nil, nil)
}
}
+ optimiseProgram(program)
+
return nil
}
-// RunProgram runs the program given the enviroment and context and returns an
+// RunProgram runs the program given the enviroment and contract and returns an
// error if the execution failed (non-consensus)
-func RunProgram(program *Program, env Environment, context *Context, input []byte) ([]byte, error) {
- return runProgram(program, 0, NewMemory(), newstack(), env, context, input)
+func RunProgram(program *Program, env Environment, contract *Contract, input []byte) ([]byte, error) {
+ return runProgram(program, 0, NewMemory(), newstack(), env, contract, input)
}
-func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env Environment, context *Context, input []byte) ([]byte, error) {
- context.Input = input
+func runProgram(program *Program, pcstart uint64, mem *Memory, stack *stack, env Environment, contract *Contract, input []byte) ([]byte, error) {
+ contract.Input = input
var (
- caller = context.caller
- statedb = env.State()
- pc int = program.mapping[pcstart]
-
- jump = func(to *big.Int) error {
- if !validDest(program.destinations, to) {
- nop := context.GetOp(to.Uint64())
- return fmt.Errorf("invalid jump destination (%v) %v", nop, to)
- }
+ pc uint64 = program.mapping[pcstart]
+ instrCount = 0
+ )
- pc = program.mapping[to.Uint64()]
+ if glog.V(logger.Debug) {
+ glog.Infof("running JIT program %x\n", program.Id[:4])
+ tstart := time.Now()
+ defer func() {
+ glog.Infof("JIT program %x done. time: %v instrc: %v\n", program.Id[:4], time.Since(tstart), instrCount)
+ }()
+ }
- return nil
- }
- )
+ for pc < uint64(len(program.instructions)) {
+ instrCount++
- for pc < len(program.instructions) {
instr := program.instructions[pc]
- // calculate the new memory size and gas price for the current executing opcode
- newMemSize, cost, err := jitCalculateGasAndSize(env, context, caller, instr, statedb, mem, stack)
+ ret, err := instr.do(program, &pc, env, contract, mem, stack)
if err != nil {
return nil, err
}
- // Use the calculated gas. When insufficient gas is present, use all gas and return an
- // Out Of Gas error
- if !context.UseGas(cost) {
- return nil, OutOfGasError
+ if instr.halts() {
+ return contract.Return(ret), nil
}
- // Resize the memory calculated previously
- mem.Resize(newMemSize.Uint64())
-
- // These opcodes return an argument and are thefor handled
- // differently from the rest of the opcodes
- switch instr.op {
- case JUMP:
- if err := jump(stack.pop()); err != nil {
- return nil, err
- }
- continue
- case JUMPI:
- pos, cond := stack.pop(), stack.pop()
-
- if cond.Cmp(common.BigTrue) >= 0 {
- if err := jump(pos); err != nil {
- return nil, err
- }
- continue
- }
- case RETURN:
- offset, size := stack.pop(), stack.pop()
- ret := mem.GetPtr(offset.Int64(), size.Int64())
-
- return context.Return(ret), nil
- case SUICIDE:
- instr.fn(instr, env, context, mem, stack)
-
- return context.Return(nil), nil
- case STOP:
- return context.Return(nil), nil
- default:
- if instr.fn == nil {
- return nil, fmt.Errorf("Invalid opcode %x", instr.op)
- }
-
- instr.fn(instr, env, context, mem, stack)
- }
-
- pc++
}
- context.Input = nil
+ contract.Input = nil
- return context.Return(nil), nil
+ return contract.Return(nil), nil
}
// validDest checks if the given distination is a valid one given the
@@ -375,7 +351,7 @@ func validDest(dests map[uint64]struct{}, dest *big.Int) bool {
// jitCalculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for
// the operation. This does not reduce gas or resizes the memory.
-func jitCalculateGasAndSize(env Environment, context *Context, caller ContextRef, instr instruction, statedb *state.StateDB, mem *Memory, stack *stack) (*big.Int, *big.Int, error) {
+func jitCalculateGasAndSize(env Environment, contract *Contract, instr instruction, statedb Database, mem *Memory, stack *stack) (*big.Int, *big.Int, error) {
var (
gas = new(big.Int)
newMemSize *big.Int = new(big.Int)
@@ -426,27 +402,25 @@ func jitCalculateGasAndSize(env Environment, context *Context, caller ContextRef
var g *big.Int
y, x := stack.data[stack.len()-2], stack.data[stack.len()-1]
- val := statedb.GetState(context.Address(), common.BigToHash(x))
+ val := statedb.GetState(contract.Address(), common.BigToHash(x))
// This checks for 3 scenario's and calculates gas accordingly
// 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 nen-zero to a non-zero (CHANGE)
if common.EmptyHash(val) && !common.EmptyHash(common.BigToHash(y)) {
- // 0 => non 0
g = params.SstoreSetGas
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) {
- statedb.Refund(params.SstoreRefundGas)
+ statedb.AddRefund(params.SstoreRefundGas)
g = params.SstoreClearGas
} else {
- // non 0 => non 0 (or 0 => 0)
g = params.SstoreClearGas
}
gas.Set(g)
case SUICIDE:
- if !statedb.IsDeleted(context.Address()) {
- statedb.Refund(params.SuicideRefundGas)
+ if !statedb.IsDeleted(contract.Address()) {
+ statedb.AddRefund(params.SuicideRefundGas)
}
case MLOAD:
newMemSize = calcMemSize(stack.peek(), u256(32))
@@ -483,7 +457,8 @@ func jitCalculateGasAndSize(env Environment, context *Context, caller ContextRef
gas.Add(gas, stack.data[stack.len()-1])
if op == CALL {
- if env.State().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil {
+ //if env.Db().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil {
+ if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) {
gas.Add(gas, params.CallNewAccountGas)
}
}
@@ -497,29 +472,7 @@ func jitCalculateGasAndSize(env Environment, context *Context, caller ContextRef
newMemSize = common.BigMax(x, y)
}
-
- if newMemSize.Cmp(common.Big0) > 0 {
- newMemSizeWords := toWordSize(newMemSize)
- newMemSize.Mul(newMemSizeWords, u256(32))
-
- if newMemSize.Cmp(u256(int64(mem.Len()))) > 0 {
- // be careful reusing variables here when changing.
- // The order has been optimised to reduce allocation
- oldSize := toWordSize(big.NewInt(int64(mem.Len())))
- pow := new(big.Int).Exp(oldSize, common.Big2, Zero)
- linCoef := oldSize.Mul(oldSize, params.MemoryGas)
- quadCoef := new(big.Int).Div(pow, params.QuadCoeffDiv)
- oldTotalFee := new(big.Int).Add(linCoef, quadCoef)
-
- pow.Exp(newMemSizeWords, common.Big2, Zero)
- linCoef = linCoef.Mul(newMemSizeWords, params.MemoryGas)
- quadCoef = quadCoef.Div(pow, params.QuadCoeffDiv)
- newTotalFee := linCoef.Add(linCoef, quadCoef)
-
- fee := newTotalFee.Sub(newTotalFee, oldTotalFee)
- gas.Add(gas, fee)
- }
- }
+ quadMemGas(mem, newMemSize, gas)
return newMemSize, gas, nil
}
diff --git a/core/vm/jit_optimiser.go b/core/vm/jit_optimiser.go
new file mode 100644
index 000000000..845ffbbdf
--- /dev/null
+++ b/core/vm/jit_optimiser.go
@@ -0,0 +1,107 @@
+package vm
+
+import (
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/logger/glog"
+)
+
+// optimeProgram optimises a JIT program creating segments out of program
+// instructions. Currently covered are multi-pushes and static jumps
+func optimiseProgram(program *Program) {
+ var load []instruction
+
+ var (
+ statsJump = 0
+ statsPush = 0
+ )
+
+ if glog.V(logger.Debug) {
+ glog.Infof("optimising %x\n", program.Id[:4])
+ tstart := time.Now()
+ defer func() {
+ glog.Infof("optimised %x done in %v with JMP: %d PSH: %d\n", program.Id[:4], time.Since(tstart), statsJump, statsPush)
+ }()
+ }
+
+ /*
+ code := Parse(program.code)
+ for _, test := range [][]OpCode{
+ []OpCode{PUSH, PUSH, ADD},
+ []OpCode{PUSH, PUSH, SUB},
+ []OpCode{PUSH, PUSH, MUL},
+ []OpCode{PUSH, PUSH, DIV},
+ } {
+ matchCount := 0
+ MatchFn(code, test, func(i int) bool {
+ matchCount++
+ return true
+ })
+ fmt.Printf("found %d match count on: %v\n", matchCount, test)
+ }
+ */
+
+ for i := 0; i < len(program.instructions); i++ {
+ instr := program.instructions[i].(instruction)
+
+ switch {
+ case instr.op.IsPush():
+ load = append(load, instr)
+ case instr.op.IsStaticJump():
+ if len(load) == 0 {
+ continue
+ }
+ // if the push load is greater than 1, finalise that
+ // segment first
+ if len(load) > 2 {
+ seg, size := makePushSeg(load[:len(load)-1])
+ program.instructions[i-size-1] = seg
+ statsPush++
+ }
+ // create a segment consisting of a pre determined
+ // jump, destination and validity.
+ seg := makeStaticJumpSeg(load[len(load)-1].data, program)
+ program.instructions[i-1] = seg
+ statsJump++
+
+ load = nil
+ default:
+ // create a new N pushes segment
+ if len(load) > 1 {
+ seg, size := makePushSeg(load)
+ program.instructions[i-size] = seg
+ statsPush++
+ }
+ load = nil
+ }
+ }
+}
+
+// makePushSeg creates a new push segment from N amount of push instructions
+func makePushSeg(instrs []instruction) (pushSeg, int) {
+ var (
+ data []*big.Int
+ gas = new(big.Int)
+ )
+
+ for _, instr := range instrs {
+ data = append(data, instr.data)
+ gas.Add(gas, instr.gas)
+ }
+
+ return pushSeg{data, gas}, len(instrs)
+}
+
+// makeStaticJumpSeg creates a new static jump segment from a predefined
+// destination (PUSH, JUMP).
+func makeStaticJumpSeg(to *big.Int, program *Program) jumpSeg {
+ gas := new(big.Int)
+ gas.Add(gas, _baseCheck[PUSH1].gas)
+ gas.Add(gas, _baseCheck[JUMP].gas)
+
+ contract := &Contract{Code: program.code}
+ pos, err := jump(program.mapping, program.destinations, contract, to)
+ return jumpSeg{pos, err, gas}
+}
diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go
index d8e442637..aa97e5184 100644
--- a/core/vm/jit_test.go
+++ b/core/vm/jit_test.go
@@ -21,13 +21,99 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/ethdb"
)
const maxRun = 1000
+func TestSegmenting(t *testing.T) {
+ prog := NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, 0x0})
+ err := CompileProgram(prog)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if instr, ok := prog.instructions[0].(pushSeg); ok {
+ if len(instr.data) != 2 {
+ t.Error("expected 2 element width pushSegment, got", len(instr.data))
+ }
+ } else {
+ t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0])
+ }
+
+ prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
+ err = CompileProgram(prog)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, ok := prog.instructions[1].(jumpSeg); ok {
+ } else {
+ t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1])
+ }
+
+ prog = NewProgram([]byte{byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(PUSH1), 0x1, byte(JUMP)})
+ err = CompileProgram(prog)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if instr, ok := prog.instructions[0].(pushSeg); ok {
+ if len(instr.data) != 2 {
+ t.Error("expected 2 element width pushSegment, got", len(instr.data))
+ }
+ } else {
+ t.Errorf("expected instr[0] to be a pushSeg, got %T", prog.instructions[0])
+ }
+ if _, ok := prog.instructions[2].(jumpSeg); ok {
+ } else {
+ t.Errorf("expected instr[1] to be jumpSeg, got %T", prog.instructions[1])
+ }
+}
+
+func TestCompiling(t *testing.T) {
+ prog := NewProgram([]byte{0x60, 0x10})
+ err := CompileProgram(prog)
+ if err != nil {
+ t.Error("didn't expect compile error")
+ }
+
+ if len(prog.instructions) != 1 {
+ t.Error("exected 1 compiled instruction, got", len(prog.instructions))
+ }
+}
+
+func TestResetInput(t *testing.T) {
+ var sender account
+
+ env := NewEnv()
+ contract := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0))
+ contract.CodeAddr = &common.Address{}
+
+ program := NewProgram([]byte{})
+ RunProgram(program, env, contract, []byte{0xbe, 0xef})
+ if contract.Input != nil {
+ t.Errorf("expected input to be nil, got %x", contract.Input)
+ }
+}
+
+func TestPcMappingToInstruction(t *testing.T) {
+ program := NewProgram([]byte{byte(PUSH2), 0xbe, 0xef, byte(ADD)})
+ CompileProgram(program)
+ if program.mapping[3] != 1 {
+ t.Error("expected mapping PC 4 to me instr no. 2, got", program.mapping[4])
+ }
+}
+
+var benchmarks = map[string]vmBench{
+ "pushes": vmBench{
+ false, false, false,
+ common.Hex2Bytes("600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01"), nil,
+ },
+}
+
+func BenchmarkPushes(b *testing.B) {
+ runVmBench(benchmarks["pushes"], b)
+}
+
type vmBench struct {
precompile bool // compile prior to executing
nojit bool // ignore jit (sets DisbaleJit = true
@@ -37,9 +123,19 @@ type vmBench struct {
input []byte
}
+type account struct{}
+
+func (account) SubBalance(amount *big.Int) {}
+func (account) AddBalance(amount *big.Int) {}
+func (account) SetBalance(*big.Int) {}
+func (account) SetNonce(uint64) {}
+func (account) Balance() *big.Int { return nil }
+func (account) Address() common.Address { return common.Address{} }
+func (account) ReturnGas(*big.Int, *big.Int) {}
+func (account) SetCode([]byte) {}
+
func runVmBench(test vmBench, b *testing.B) {
- db, _ := ethdb.NewMemDatabase()
- sender := state.NewStateObject(common.Address{}, db)
+ var sender account
if test.precompile && !test.forcejit {
NewProgram(test.code)
@@ -52,7 +148,7 @@ func runVmBench(test vmBench, b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
- context := NewContext(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0))
+ context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0))
context.Code = test.code
context.CodeAddr = &common.Address{}
_, err := New(env).Run(context, test.input)
@@ -63,17 +159,6 @@ func runVmBench(test vmBench, b *testing.B) {
}
}
-var benchmarks = map[string]vmBench{
- "pushes": vmBench{
- false, false, false,
- common.Hex2Bytes("600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01600a600a01"), nil,
- },
-}
-
-func BenchmarkPushes(b *testing.B) {
- runVmBench(benchmarks["pushes"], b)
-}
-
type Env struct {
gasLimit *big.Int
depth int
@@ -93,30 +178,30 @@ func (self *Env) StructLogs() []StructLog {
//func (self *Env) PrevHash() []byte { return self.parent }
func (self *Env) Coinbase() common.Address { return common.Address{} }
+func (self *Env) MakeSnapshot() Database { return nil }
+func (self *Env) SetSnapshot(Database) {}
func (self *Env) Time() *big.Int { return big.NewInt(time.Now().Unix()) }
func (self *Env) Difficulty() *big.Int { return big.NewInt(0) }
-func (self *Env) State() *state.StateDB { return nil }
+func (self *Env) Db() Database { return nil }
func (self *Env) GasLimit() *big.Int { return self.gasLimit }
func (self *Env) VmType() Type { return StdVmTy }
func (self *Env) GetHash(n uint64) common.Hash {
return common.BytesToHash(crypto.Sha3([]byte(big.NewInt(int64(n)).String())))
}
-func (self *Env) AddLog(log *state.Log) {
+func (self *Env) AddLog(log *Log) {
}
func (self *Env) Depth() int { return self.depth }
func (self *Env) SetDepth(i int) { self.depth = i }
-func (self *Env) CanTransfer(from Account, balance *big.Int) bool {
- return from.Balance().Cmp(balance) >= 0
-}
-func (self *Env) Transfer(from, to Account, amount *big.Int) error {
- return nil
+func (self *Env) CanTransfer(from common.Address, balance *big.Int) bool {
+ return true
}
-func (self *Env) Call(caller ContextRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
+func (self *Env) Transfer(from, to Account, amount *big.Int) {}
+func (self *Env) Call(caller ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
return nil, nil
}
-func (self *Env) CallCode(caller ContextRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
+func (self *Env) CallCode(caller ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
return nil, nil
}
-func (self *Env) Create(caller ContextRef, data []byte, gas, price, value *big.Int) ([]byte, error, ContextRef) {
- return nil, nil, nil
+func (self *Env) Create(caller ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
+ return nil, common.Address{}, nil
}
diff --git a/core/vm/jit_util.go b/core/vm/jit_util.go
new file mode 100644
index 000000000..0d3d6d701
--- /dev/null
+++ b/core/vm/jit_util.go
@@ -0,0 +1,68 @@
+// Copyright 2014 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
+
+// Parse parses all opcodes from the given code byte slice. This function
+// performs no error checking and may return non-existing opcodes.
+func Parse(code []byte) (opcodes []OpCode) {
+ for pc := uint64(0); pc < uint64(len(code)); pc++ {
+ op := OpCode(code[pc])
+
+ 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:
+ a := uint64(op) - uint64(PUSH1) + 1
+ pc += a
+ opcodes = append(opcodes, PUSH)
+ case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
+ opcodes = append(opcodes, DUP)
+ case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
+ opcodes = append(opcodes, SWAP)
+ default:
+ opcodes = append(opcodes, op)
+ }
+ }
+
+ return opcodes
+}
+
+// MatchFn searcher for match in the given input and calls matcheFn if it finds
+// an appropriate match. matcherFn yields the starting position in the input.
+// MatchFn will continue to search for a match until it reacher the end of the
+// buffer or if matcherFn return false.
+func MatchFn(input, match []OpCode, matcherFn func(int) bool) {
+ // short circuit if either input or match is empty or if the match is
+ // greater than the input
+ if len(input) == 0 || len(match) == 0 || len(match) > len(input) {
+ return
+ }
+
+main:
+ for i, op := range input[:len(input)+1-len(match)] {
+ // match first opcode and continue search
+ if op == match[0] {
+ for j := 1; j < len(match); j++ {
+ if input[i+j] != match[j] {
+ continue main
+ }
+ }
+ // check for abort instruction
+ if !matcherFn(i) {
+ return
+ }
+ }
+ }
+}
diff --git a/core/vm/jit_util_test.go b/core/vm/jit_util_test.go
new file mode 100644
index 000000000..686bee0ac
--- /dev/null
+++ b/core/vm/jit_util_test.go
@@ -0,0 +1,84 @@
+// Copyright 2014 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"
+
+type matchTest struct {
+ input []OpCode
+ match []OpCode
+ matches int
+}
+
+func TestMatchFn(t *testing.T) {
+ tests := []matchTest{
+ matchTest{
+ []OpCode{PUSH1, PUSH1, MSTORE, JUMP},
+ []OpCode{PUSH1, MSTORE},
+ 1,
+ },
+ matchTest{
+ []OpCode{PUSH1, PUSH1, MSTORE, JUMP},
+ []OpCode{PUSH1, MSTORE, PUSH1},
+ 0,
+ },
+ matchTest{
+ []OpCode{},
+ []OpCode{PUSH1},
+ 0,
+ },
+ }
+
+ for i, test := range tests {
+ var matchCount int
+ MatchFn(test.input, test.match, func(i int) bool {
+ matchCount++
+ return true
+ })
+ if matchCount != test.matches {
+ t.Errorf("match count failed on test[%d]: expected %d matches, got %d", i, test.matches, matchCount)
+ }
+ }
+}
+
+type parseTest struct {
+ base OpCode
+ size int
+ output OpCode
+}
+
+func TestParser(t *testing.T) {
+ tests := []parseTest{
+ parseTest{PUSH1, 32, PUSH},
+ parseTest{DUP1, 16, DUP},
+ parseTest{SWAP1, 16, SWAP},
+ parseTest{MSTORE, 1, MSTORE},
+ }
+
+ for _, test := range tests {
+ for i := 0; i < test.size; i++ {
+ code := append([]byte{byte(byte(test.base) + byte(i))}, make([]byte, i+1)...)
+ output := Parse(code)
+ if len(output) == 0 {
+ t.Fatal("empty output")
+ }
+ if output[0] != test.output {
+ t.Error("%v failed: expected %v but got %v", test.base+OpCode(i), output[0])
+ }
+ }
+ }
+}
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
new file mode 100644
index 000000000..ab899647f
--- /dev/null
+++ b/core/vm/jump_table.go
@@ -0,0 +1,143 @@
+package vm
+
+import "math/big"
+
+type jumpPtr struct {
+ fn instrFn
+ valid bool
+}
+
+var jumpTable [256]jumpPtr
+
+func init() {
+ jumpTable[ADD] = jumpPtr{opAdd, true}
+ jumpTable[SUB] = jumpPtr{opSub, true}
+ jumpTable[MUL] = jumpPtr{opMul, true}
+ jumpTable[DIV] = jumpPtr{opDiv, true}
+ jumpTable[SDIV] = jumpPtr{opSdiv, true}
+ jumpTable[MOD] = jumpPtr{opMod, true}
+ jumpTable[SMOD] = jumpPtr{opSmod, true}
+ jumpTable[EXP] = jumpPtr{opExp, true}
+ jumpTable[SIGNEXTEND] = jumpPtr{opSignExtend, true}
+ jumpTable[NOT] = jumpPtr{opNot, true}
+ jumpTable[LT] = jumpPtr{opLt, true}
+ jumpTable[GT] = jumpPtr{opGt, true}
+ jumpTable[SLT] = jumpPtr{opSlt, true}
+ jumpTable[SGT] = jumpPtr{opSgt, true}
+ jumpTable[EQ] = jumpPtr{opEq, true}
+ jumpTable[ISZERO] = jumpPtr{opIszero, true}
+ jumpTable[AND] = jumpPtr{opAnd, true}
+ jumpTable[OR] = jumpPtr{opOr, true}
+ jumpTable[XOR] = jumpPtr{opXor, true}
+ jumpTable[BYTE] = jumpPtr{opByte, true}
+ jumpTable[ADDMOD] = jumpPtr{opAddmod, true}
+ jumpTable[MULMOD] = jumpPtr{opMulmod, true}
+ jumpTable[SHA3] = jumpPtr{opSha3, true}
+ jumpTable[ADDRESS] = jumpPtr{opAddress, true}
+ jumpTable[BALANCE] = jumpPtr{opBalance, true}
+ jumpTable[ORIGIN] = jumpPtr{opOrigin, true}
+ jumpTable[CALLER] = jumpPtr{opCaller, true}
+ jumpTable[CALLVALUE] = jumpPtr{opCallValue, true}
+ jumpTable[CALLDATALOAD] = jumpPtr{opCalldataLoad, true}
+ jumpTable[CALLDATASIZE] = jumpPtr{opCalldataSize, true}
+ jumpTable[CALLDATACOPY] = jumpPtr{opCalldataCopy, true}
+ jumpTable[CODESIZE] = jumpPtr{opCodeSize, true}
+ jumpTable[EXTCODESIZE] = jumpPtr{opExtCodeSize, true}
+ jumpTable[CODECOPY] = jumpPtr{opCodeCopy, true}
+ jumpTable[EXTCODECOPY] = jumpPtr{opExtCodeCopy, true}
+ jumpTable[GASPRICE] = jumpPtr{opGasprice, true}
+ jumpTable[BLOCKHASH] = jumpPtr{opBlockhash, true}
+ jumpTable[COINBASE] = jumpPtr{opCoinbase, true}
+ jumpTable[TIMESTAMP] = jumpPtr{opTimestamp, true}
+ jumpTable[NUMBER] = jumpPtr{opNumber, true}
+ jumpTable[DIFFICULTY] = jumpPtr{opDifficulty, true}
+ jumpTable[GASLIMIT] = jumpPtr{opGasLimit, true}
+ jumpTable[POP] = jumpPtr{opPop, true}
+ jumpTable[MLOAD] = jumpPtr{opMload, true}
+ jumpTable[MSTORE] = jumpPtr{opMstore, true}
+ jumpTable[MSTORE8] = jumpPtr{opMstore8, true}
+ jumpTable[SLOAD] = jumpPtr{opSload, true}
+ jumpTable[SSTORE] = jumpPtr{opSstore, true}
+ jumpTable[JUMPDEST] = jumpPtr{opJumpdest, true}
+ jumpTable[PC] = jumpPtr{nil, true}
+ jumpTable[MSIZE] = jumpPtr{opMsize, true}
+ jumpTable[GAS] = jumpPtr{opGas, true}
+ jumpTable[CREATE] = jumpPtr{opCreate, true}
+ jumpTable[CALL] = jumpPtr{opCall, true}
+ jumpTable[CALLCODE] = jumpPtr{opCallCode, true}
+ jumpTable[LOG0] = jumpPtr{makeLog(0), true}
+ jumpTable[LOG1] = jumpPtr{makeLog(1), true}
+ jumpTable[LOG2] = jumpPtr{makeLog(2), true}
+ jumpTable[LOG3] = jumpPtr{makeLog(3), true}
+ jumpTable[LOG4] = jumpPtr{makeLog(4), true}
+ jumpTable[SWAP1] = jumpPtr{makeSwap(1), true}
+ jumpTable[SWAP2] = jumpPtr{makeSwap(2), true}
+ jumpTable[SWAP3] = jumpPtr{makeSwap(3), true}
+ jumpTable[SWAP4] = jumpPtr{makeSwap(4), true}
+ jumpTable[SWAP5] = jumpPtr{makeSwap(5), true}
+ jumpTable[SWAP6] = jumpPtr{makeSwap(6), true}
+ jumpTable[SWAP7] = jumpPtr{makeSwap(7), true}
+ jumpTable[SWAP8] = jumpPtr{makeSwap(8), true}
+ jumpTable[SWAP9] = jumpPtr{makeSwap(9), true}
+ jumpTable[SWAP10] = jumpPtr{makeSwap(10), true}
+ jumpTable[SWAP11] = jumpPtr{makeSwap(11), true}
+ jumpTable[SWAP12] = jumpPtr{makeSwap(12), true}
+ jumpTable[SWAP13] = jumpPtr{makeSwap(13), true}
+ jumpTable[SWAP14] = jumpPtr{makeSwap(14), true}
+ jumpTable[SWAP15] = jumpPtr{makeSwap(15), true}
+ jumpTable[SWAP16] = jumpPtr{makeSwap(16), true}
+ jumpTable[PUSH1] = jumpPtr{makePush(1, big.NewInt(1)), true}
+ jumpTable[PUSH2] = jumpPtr{makePush(2, big.NewInt(2)), true}
+ jumpTable[PUSH3] = jumpPtr{makePush(3, big.NewInt(3)), true}
+ jumpTable[PUSH4] = jumpPtr{makePush(4, big.NewInt(4)), true}
+ jumpTable[PUSH5] = jumpPtr{makePush(5, big.NewInt(5)), true}
+ jumpTable[PUSH6] = jumpPtr{makePush(6, big.NewInt(6)), true}
+ jumpTable[PUSH7] = jumpPtr{makePush(7, big.NewInt(7)), true}
+ jumpTable[PUSH8] = jumpPtr{makePush(8, big.NewInt(8)), true}
+ jumpTable[PUSH9] = jumpPtr{makePush(9, big.NewInt(9)), true}
+ jumpTable[PUSH10] = jumpPtr{makePush(10, big.NewInt(10)), true}
+ jumpTable[PUSH11] = jumpPtr{makePush(11, big.NewInt(11)), true}
+ jumpTable[PUSH12] = jumpPtr{makePush(12, big.NewInt(12)), true}
+ jumpTable[PUSH13] = jumpPtr{makePush(13, big.NewInt(13)), true}
+ jumpTable[PUSH14] = jumpPtr{makePush(14, big.NewInt(14)), true}
+ jumpTable[PUSH15] = jumpPtr{makePush(15, big.NewInt(15)), true}
+ jumpTable[PUSH16] = jumpPtr{makePush(16, big.NewInt(16)), true}
+ jumpTable[PUSH17] = jumpPtr{makePush(17, big.NewInt(17)), true}
+ jumpTable[PUSH18] = jumpPtr{makePush(18, big.NewInt(18)), true}
+ jumpTable[PUSH19] = jumpPtr{makePush(19, big.NewInt(19)), true}
+ jumpTable[PUSH20] = jumpPtr{makePush(20, big.NewInt(20)), true}
+ jumpTable[PUSH21] = jumpPtr{makePush(21, big.NewInt(21)), true}
+ jumpTable[PUSH22] = jumpPtr{makePush(22, big.NewInt(22)), true}
+ jumpTable[PUSH23] = jumpPtr{makePush(23, big.NewInt(23)), true}
+ jumpTable[PUSH24] = jumpPtr{makePush(24, big.NewInt(24)), true}
+ jumpTable[PUSH25] = jumpPtr{makePush(25, big.NewInt(25)), true}
+ jumpTable[PUSH26] = jumpPtr{makePush(26, big.NewInt(26)), true}
+ jumpTable[PUSH27] = jumpPtr{makePush(27, big.NewInt(27)), true}
+ jumpTable[PUSH28] = jumpPtr{makePush(28, big.NewInt(28)), true}
+ jumpTable[PUSH29] = jumpPtr{makePush(29, big.NewInt(29)), true}
+ jumpTable[PUSH30] = jumpPtr{makePush(30, big.NewInt(30)), true}
+ jumpTable[PUSH31] = jumpPtr{makePush(31, big.NewInt(31)), true}
+ jumpTable[PUSH32] = jumpPtr{makePush(32, big.NewInt(32)), true}
+ jumpTable[DUP1] = jumpPtr{makeDup(1), true}
+ jumpTable[DUP2] = jumpPtr{makeDup(2), true}
+ jumpTable[DUP3] = jumpPtr{makeDup(3), true}
+ jumpTable[DUP4] = jumpPtr{makeDup(4), true}
+ jumpTable[DUP5] = jumpPtr{makeDup(5), true}
+ jumpTable[DUP6] = jumpPtr{makeDup(6), true}
+ jumpTable[DUP7] = jumpPtr{makeDup(7), true}
+ jumpTable[DUP8] = jumpPtr{makeDup(8), true}
+ jumpTable[DUP9] = jumpPtr{makeDup(9), true}
+ jumpTable[DUP10] = jumpPtr{makeDup(10), true}
+ jumpTable[DUP11] = jumpPtr{makeDup(11), true}
+ jumpTable[DUP12] = jumpPtr{makeDup(12), true}
+ jumpTable[DUP13] = jumpPtr{makeDup(13), true}
+ jumpTable[DUP14] = jumpPtr{makeDup(14), true}
+ jumpTable[DUP15] = jumpPtr{makeDup(15), true}
+ jumpTable[DUP16] = jumpPtr{makeDup(16), true}
+
+ jumpTable[RETURN] = jumpPtr{nil, true}
+ jumpTable[SUICIDE] = jumpPtr{nil, true}
+ jumpTable[JUMP] = jumpPtr{nil, true}
+ jumpTable[JUMPI] = jumpPtr{nil, true}
+ jumpTable[STOP] = jumpPtr{nil, true}
+}
diff --git a/core/state/log.go b/core/vm/log.go
index 5d7d7357d..191e3a253 100644
--- a/core/state/log.go
+++ b/core/vm/log.go
@@ -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 state
+package vm
import (
"fmt"
@@ -25,42 +25,47 @@ import (
)
type Log struct {
+ // Consensus fields
Address common.Address
Topics []common.Hash
Data []byte
- Number uint64
- TxHash common.Hash
- TxIndex uint
- BlockHash common.Hash
- Index uint
+ // Derived fields (don't reorder!)
+ BlockNumber uint64
+ TxHash common.Hash
+ TxIndex uint
+ BlockHash common.Hash
+ Index uint
}
func NewLog(address common.Address, topics []common.Hash, data []byte, number uint64) *Log {
- return &Log{Address: address, Topics: topics, Data: data, Number: number}
+ return &Log{Address: address, Topics: topics, Data: data, BlockNumber: number}
}
-func (self *Log) EncodeRLP(w io.Writer) error {
- return rlp.Encode(w, []interface{}{self.Address, self.Topics, self.Data})
+func (l *Log) EncodeRLP(w io.Writer) error {
+ return rlp.Encode(w, []interface{}{l.Address, l.Topics, l.Data})
}
-func (self *Log) String() string {
- return fmt.Sprintf(`log: %x %x %x %x %d %x %d`, self.Address, self.Topics, self.Data, self.TxHash, self.TxIndex, self.BlockHash, self.Index)
+func (l *Log) DecodeRLP(s *rlp.Stream) error {
+ var log struct {
+ Address common.Address
+ Topics []common.Hash
+ Data []byte
+ }
+ if err := s.Decode(&log); err != nil {
+ return err
+ }
+ l.Address, l.Topics, l.Data = log.Address, log.Topics, log.Data
+ return nil
+}
+
+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)
}
type Logs []*Log
+// LogForStorage is a wrapper around a Log that flattens and parses the entire
+// content of a log, as opposed to only the consensus fields originally (by hiding
+// the rlp interface methods).
type LogForStorage Log
-
-func (self *LogForStorage) EncodeRLP(w io.Writer) error {
- return rlp.Encode(w, []interface{}{
- self.Address,
- self.Topics,
- self.Data,
- self.Number,
- self.TxHash,
- self.TxIndex,
- self.BlockHash,
- self.Index,
- })
-}
diff --git a/core/vm/logger.go b/core/vm/logger.go
index 736f595f6..2bd02319f 100644
--- a/core/vm/logger.go
+++ b/core/vm/logger.go
@@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/common"
)
+// StdErrFormat formats a slice of StructLogs to human readable format
func StdErrFormat(logs []StructLog) {
fmt.Fprintf(os.Stderr, "VM STAT %d OPs\n", len(logs))
for _, log := range logs {
diff --git a/core/vm/memory.go b/core/vm/memory.go
index 0109050d7..d01188417 100644
--- a/core/vm/memory.go
+++ b/core/vm/memory.go
@@ -18,6 +18,7 @@ package vm
import "fmt"
+// Memory implements a simple memory model for the ethereum virtual machine.
type Memory struct {
store []byte
}
@@ -26,6 +27,7 @@ func NewMemory() *Memory {
return &Memory{nil}
}
+// 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
@@ -40,12 +42,14 @@ func (m *Memory) Set(offset, size uint64, value []byte) {
}
}
+// Resize resizes the memory to size
func (m *Memory) Resize(size uint64) {
if uint64(m.Len()) < size {
m.store = append(m.store, make([]byte, size-uint64(m.Len()))...)
}
}
+// Get returns offset + size as a new slice
func (self *Memory) Get(offset, size int64) (cpy []byte) {
if size == 0 {
return nil
@@ -61,6 +65,7 @@ func (self *Memory) Get(offset, size int64) (cpy []byte) {
return
}
+// GetPtr returns the offset + size
func (self *Memory) GetPtr(offset, size int64) []byte {
if size == 0 {
return nil
@@ -73,10 +78,12 @@ func (self *Memory) GetPtr(offset, size int64) []byte {
return nil
}
+// Len returns the length of the backing slice
func (m *Memory) Len() int {
return len(m.store)
}
+// Data returns the backing slice
func (m *Memory) Data() []byte {
return m.store
}
diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go
index ecced3650..dc4139092 100644
--- a/core/vm/opcodes.go
+++ b/core/vm/opcodes.go
@@ -20,9 +20,21 @@ import (
"fmt"
)
+// OpCode is an EVM opcode
type OpCode byte
-// Op codes
+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:
+ return true
+ }
+ return false
+}
+
+func (op OpCode) IsStaticJump() bool {
+ return op == JUMP
+}
+
const (
// 0x0 range - arithmetic ops
STOP OpCode = iota
@@ -175,6 +187,13 @@ const (
LOG4
)
+// unofficial opcodes used for parsing
+const (
+ PUSH OpCode = 0xb0 + iota
+ DUP
+ SWAP
+)
+
const (
// 0xf0 range - closures
CREATE OpCode = 0xf0 + iota
@@ -182,7 +201,6 @@ const (
CALLCODE
RETURN
- // 0x70 range - other
SUICIDE = 0xff
)
@@ -335,9 +353,11 @@ var opCodeToString = map[OpCode]string{
CALL: "CALL",
RETURN: "RETURN",
CALLCODE: "CALLCODE",
+ SUICIDE: "SUICIDE",
- // 0x70 range - other
- SUICIDE: "SUICIDE",
+ PUSH: "PUSH",
+ DUP: "DUP",
+ SWAP: "SWAP",
}
func (o OpCode) String() string {
diff --git a/core/vm/segments.go b/core/vm/segments.go
new file mode 100644
index 000000000..fd4065149
--- /dev/null
+++ b/core/vm/segments.go
@@ -0,0 +1,44 @@
+package vm
+
+import "math/big"
+
+type jumpSeg struct {
+ pos uint64
+ err error
+ gas *big.Int
+}
+
+func (j jumpSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error) {
+ if !contract.UseGas(j.gas) {
+ return nil, OutOfGasError
+ }
+ if j.err != nil {
+ return nil, j.err
+ }
+ *pc = j.pos
+ return nil, nil
+}
+func (s jumpSeg) halts() bool { return false }
+func (s jumpSeg) Op() OpCode { return 0 }
+
+type pushSeg struct {
+ data []*big.Int
+ gas *big.Int
+}
+
+func (s pushSeg) do(program *Program, pc *uint64, env Environment, contract *Contract, memory *Memory, stack *stack) ([]byte, error) {
+ // Use the calculated gas. When insufficient gas is present, use all gas and return an
+ // Out Of Gas error
+ if !contract.UseGas(s.gas) {
+ return nil, OutOfGasError
+ }
+
+ for _, d := range s.data {
+ stack.push(new(big.Int).Set(d))
+ }
+ *pc += uint64(len(s.data))
+ return nil, nil
+}
+
+func (s pushSeg) halts() bool { return false }
+func (s pushSeg) Op() OpCode { return 0 }
diff --git a/core/vm/settings.go b/core/vm/settings.go
deleted file mode 100644
index f9296f6c8..000000000
--- a/core/vm/settings.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2014 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
-
-var (
- EnableJit bool // Enables the JIT VM
- ForceJit bool // Force the JIT, skip byte VM
- MaxProgSize int // Max cache size for JIT Programs
-)
-
-const defaultJitMaxCache int = 64
diff --git a/core/vm/stack.go b/core/vm/stack.go
index 009ac9e1b..0046edec2 100644
--- a/core/vm/stack.go
+++ b/core/vm/stack.go
@@ -42,6 +42,9 @@ func (st *stack) push(d *big.Int) {
//st.data = append(st.data, stackItem)
st.data = append(st.data, d)
}
+func (st *stack) pushN(ds ...*big.Int) {
+ st.data = append(st.data, ds...)
+}
func (st *stack) pop() (ret *big.Int) {
ret = st.data[len(st.data)-1]
diff --git a/core/vm/virtual_machine.go b/core/vm/virtual_machine.go
index 047723744..9b3340bb2 100644
--- a/core/vm/virtual_machine.go
+++ b/core/vm/virtual_machine.go
@@ -16,7 +16,8 @@
package vm
+// VirtualMachine is an EVM interface
type VirtualMachine interface {
Env() Environment
- Run(context *Context, data []byte) ([]byte, error)
+ Run(*Contract, []byte) ([]byte, error)
}
diff --git a/core/vm/vm.go b/core/vm/vm.go
index d9e1a0ce5..4b03e55f0 100644
--- a/core/vm/vm.go
+++ b/core/vm/vm.go
@@ -14,33 +14,32 @@
// 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 implements the Ethereum Virtual Machine.
package vm
import (
"fmt"
"math/big"
+ "time"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/params"
)
-// Vm implements VirtualMachine
+// Vm is an EVM and implements VirtualMachine
type Vm struct {
env Environment
}
-// New returns a new Virtual Machine
+// New returns a new Vm
func New(env Environment) *Vm {
return &Vm{env: env}
}
// Run loops and evaluates the contract's code with the given input data
-func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
+func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
self.env.SetDepth(self.env.Depth() + 1)
defer self.env.SetDepth(self.env.Depth() - 1)
@@ -48,42 +47,48 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
defer func() {
if err != nil {
// In case of a VM exception (known exceptions) all gas consumed (panics NOT included).
- context.UseGas(context.Gas)
+ contract.UseGas(contract.Gas)
- ret = context.Return(nil)
+ ret = contract.Return(nil)
}
}()
- if context.CodeAddr != nil {
- if p := Precompiled[context.CodeAddr.Str()]; p != nil {
- return self.RunPrecompiled(p, input, context)
+ if contract.CodeAddr != nil {
+ if p := Precompiled[contract.CodeAddr.Str()]; p != nil {
+ return self.RunPrecompiled(p, input, contract)
}
}
+ // Don't bother with the execution if there's no code.
+ if len(contract.Code) == 0 {
+ return contract.Return(nil), nil
+ }
+
var (
- codehash = crypto.Sha3Hash(context.Code) // codehash is used when doing jump dest caching
+ codehash = crypto.Sha3Hash(contract.Code) // codehash is used when doing jump dest caching
program *Program
)
if EnableJit {
- // Fetch program status.
- // * If ready run using JIT
- // * If unknown, compile in a seperate goroutine
- // * If forced wait for compilation and run once done
- if status := GetProgramStatus(codehash); status == progReady {
- return RunProgram(GetProgram(codehash), self.env, context, input)
- } else if status == progUnknown {
+ // If the JIT is enabled check the status of the JIT program,
+ // if it doesn't exist compile a new program in a seperate
+ // goroutine or wait for compilation to finish if the JIT is
+ // forced.
+ switch GetProgramStatus(codehash) {
+ case progReady:
+ return RunProgram(GetProgram(codehash), self.env, contract, input)
+ case progUnknown:
if ForceJit {
// Create and compile program
- program = NewProgram(context.Code)
+ program = NewProgram(contract.Code)
perr := CompileProgram(program)
if perr == nil {
- return RunProgram(program, self.env, context, input)
+ return RunProgram(program, self.env, contract, input)
}
glog.V(logger.Info).Infoln("error compiling program", err)
} else {
// create and compile the program. Compilation
// is done in a seperate goroutine
- program = NewProgram(context.Code)
+ program = NewProgram(contract.Code)
go func() {
err := CompileProgram(program)
if err != nil {
@@ -96,15 +101,14 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
}
var (
- caller = context.caller
- code = context.Code
- value = context.value
- price = context.Price
-
- op OpCode // current opcode
- mem = NewMemory() // bound memory
- stack = newstack() // local stack
- statedb = self.env.State() // current state
+ caller = contract.caller
+ code = contract.Code
+ instrCount = 0
+
+ op OpCode // current opcode
+ mem = NewMemory() // bound memory
+ stack = newstack() // local stack
+ statedb = self.env.Db() // current state
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC to be uint256. Pratically much less so feasible.
pc = uint64(0) // program counter
@@ -112,8 +116,8 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
// jump evaluates and checks whether the given jump destination is a valid one
// if valid move the `pc` otherwise return an error.
jump = func(from uint64, to *big.Int) error {
- if !context.jumpdests.has(codehash, code, to) {
- nop := context.GetOp(to.Uint64())
+ if !contract.jumpdests.has(codehash, code, to) {
+ nop := contract.GetOp(to.Uint64())
return fmt.Errorf("invalid jump destination (%v) %v", nop, to)
}
@@ -125,552 +129,92 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
newMemSize *big.Int
cost *big.Int
)
+ contract.Input = input
// User defer pattern to check for an error and, based on the error being nil or not, use all gas and return.
defer func() {
if err != nil {
- self.log(pc, op, context.Gas, cost, mem, stack, context, err)
+ self.log(pc, op, contract.Gas, cost, mem, stack, contract, err)
}
}()
- // Don't bother with the execution if there's no code.
- if len(code) == 0 {
- return context.Return(nil), nil
+ if glog.V(logger.Debug) {
+ glog.Infof("running byte VM %x\n", codehash[:4])
+ tstart := time.Now()
+ defer func() {
+ glog.Infof("byte VM %x done. time: %v instrc: %v\n", codehash[:4], time.Since(tstart), instrCount)
+ }()
}
- for {
- // Overhead of the atomic read might not be worth it
- /* TODO this still causes a few issues in the tests
- if program != nil && progStatus(atomic.LoadInt32(&program.status)) == progReady {
- // move execution
- glog.V(logger.Info).Infoln("Moved execution to JIT")
- return runProgram(program, pc, mem, stack, self.env, context, input)
- }
+ for ; ; instrCount++ {
+ /*
+ if EnableJit && it%100 == 0 {
+ if program != nil && progStatus(atomic.LoadInt32(&program.status)) == progReady {
+ // move execution
+ fmt.Println("moved", it)
+ glog.V(logger.Info).Infoln("Moved execution to JIT")
+ return runProgram(program, pc, mem, stack, self.env, contract, input)
+ }
+ }
*/
- // The base for all big integer arithmetic
- base := new(big.Int)
// Get the memory location of pc
- op = context.GetOp(pc)
+ op = contract.GetOp(pc)
// calculate the new memory size and gas price for the current executing opcode
- newMemSize, cost, err = calculateGasAndSize(self.env, context, caller, op, statedb, mem, stack)
+ newMemSize, cost, err = calculateGasAndSize(self.env, contract, caller, op, statedb, mem, stack)
if err != nil {
return nil, err
}
// Use the calculated gas. When insufficient gas is present, use all gas and return an
// Out Of Gas error
- if !context.UseGas(cost) {
+ if !contract.UseGas(cost) {
return nil, OutOfGasError
}
// Resize the memory calculated previously
mem.Resize(newMemSize.Uint64())
// Add a log message
- self.log(pc, op, context.Gas, cost, mem, stack, context, nil)
-
- switch op {
- case ADD:
- x, y := stack.pop(), stack.pop()
-
- base.Add(x, y)
-
- U256(base)
-
- // pop result back on the stack
- stack.push(base)
- case SUB:
- x, y := stack.pop(), stack.pop()
-
- base.Sub(x, y)
-
- U256(base)
-
- // pop result back on the stack
- stack.push(base)
- case MUL:
- x, y := stack.pop(), stack.pop()
-
- base.Mul(x, y)
-
- U256(base)
-
- // pop result back on the stack
- stack.push(base)
- case DIV:
- x, y := stack.pop(), stack.pop()
-
- if y.Cmp(common.Big0) != 0 {
- base.Div(x, y)
- }
-
- U256(base)
-
- // pop result back on the stack
- stack.push(base)
- case SDIV:
- x, y := S256(stack.pop()), S256(stack.pop())
-
- if y.Cmp(common.Big0) == 0 {
- base.Set(common.Big0)
- } else {
- n := new(big.Int)
- if new(big.Int).Mul(x, y).Cmp(common.Big0) < 0 {
- n.SetInt64(-1)
- } else {
- n.SetInt64(1)
- }
-
- base.Div(x.Abs(x), y.Abs(y)).Mul(base, n)
-
- U256(base)
- }
-
- stack.push(base)
- case MOD:
- x, y := stack.pop(), stack.pop()
-
- if y.Cmp(common.Big0) == 0 {
- base.Set(common.Big0)
- } else {
- base.Mod(x, y)
- }
-
- U256(base)
-
- stack.push(base)
- case SMOD:
- x, y := S256(stack.pop()), S256(stack.pop())
-
- if y.Cmp(common.Big0) == 0 {
- base.Set(common.Big0)
- } else {
- n := new(big.Int)
- if x.Cmp(common.Big0) < 0 {
- n.SetInt64(-1)
- } else {
- n.SetInt64(1)
- }
-
- base.Mod(x.Abs(x), y.Abs(y)).Mul(base, n)
-
- U256(base)
- }
-
- stack.push(base)
-
- case EXP:
- x, y := stack.pop(), stack.pop()
-
- base.Exp(x, y, Pow256)
-
- U256(base)
-
- stack.push(base)
- case SIGNEXTEND:
- back := stack.pop()
- if back.Cmp(big.NewInt(31)) < 0 {
- bit := uint(back.Uint64()*8 + 7)
- num := stack.pop()
- mask := new(big.Int).Lsh(common.Big1, bit)
- mask.Sub(mask, common.Big1)
- if common.BitTest(num, int(bit)) {
- num.Or(num, mask.Not(mask))
- } else {
- num.And(num, mask)
- }
-
- num = U256(num)
-
- stack.push(num)
- }
- case NOT:
- stack.push(U256(new(big.Int).Not(stack.pop())))
- case LT:
- x, y := stack.pop(), stack.pop()
-
- // x < y
- if x.Cmp(y) < 0 {
- stack.push(common.BigTrue)
- } else {
- stack.push(common.BigFalse)
- }
- case GT:
- x, y := stack.pop(), stack.pop()
-
- // x > y
- if x.Cmp(y) > 0 {
- stack.push(common.BigTrue)
- } else {
- stack.push(common.BigFalse)
- }
-
- case SLT:
- x, y := S256(stack.pop()), S256(stack.pop())
-
- // x < y
- if x.Cmp(S256(y)) < 0 {
- stack.push(common.BigTrue)
- } else {
- stack.push(common.BigFalse)
- }
- case SGT:
- x, y := S256(stack.pop()), S256(stack.pop())
-
- // x > y
- if x.Cmp(y) > 0 {
- stack.push(common.BigTrue)
- } else {
- stack.push(common.BigFalse)
- }
-
- case EQ:
- x, y := stack.pop(), stack.pop()
-
- // x == y
- if x.Cmp(y) == 0 {
- stack.push(common.BigTrue)
- } else {
- stack.push(common.BigFalse)
- }
- case ISZERO:
- x := stack.pop()
- if x.Cmp(common.BigFalse) > 0 {
- stack.push(common.BigFalse)
- } else {
- stack.push(common.BigTrue)
- }
-
- case AND:
- x, y := stack.pop(), stack.pop()
-
- stack.push(base.And(x, y))
- case OR:
- x, y := stack.pop(), stack.pop()
-
- stack.push(base.Or(x, y))
- case XOR:
- x, y := stack.pop(), stack.pop()
-
- stack.push(base.Xor(x, y))
- case BYTE:
- th, val := stack.pop(), stack.pop()
-
- if th.Cmp(big.NewInt(32)) < 0 {
- byt := big.NewInt(int64(common.LeftPadBytes(val.Bytes(), 32)[th.Int64()]))
-
- base.Set(byt)
- } else {
- base.Set(common.BigFalse)
- }
-
- stack.push(base)
- case ADDMOD:
- x := stack.pop()
- y := stack.pop()
- z := stack.pop()
-
- if z.Cmp(Zero) > 0 {
- add := new(big.Int).Add(x, y)
- base.Mod(add, z)
-
- base = U256(base)
- }
-
- stack.push(base)
- case MULMOD:
- x := stack.pop()
- y := stack.pop()
- z := stack.pop()
-
- if z.Cmp(Zero) > 0 {
- mul := new(big.Int).Mul(x, y)
- base.Mod(mul, z)
-
- U256(base)
- }
-
- stack.push(base)
-
- case SHA3:
- offset, size := stack.pop(), stack.pop()
- data := crypto.Sha3(mem.Get(offset.Int64(), size.Int64()))
-
- stack.push(common.BigD(data))
-
- case ADDRESS:
- stack.push(common.Bytes2Big(context.Address().Bytes()))
-
- case BALANCE:
- addr := common.BigToAddress(stack.pop())
- balance := statedb.GetBalance(addr)
+ self.log(pc, op, contract.Gas, cost, mem, stack, contract, nil)
- stack.push(new(big.Int).Set(balance))
-
- case ORIGIN:
- origin := self.env.Origin()
-
- stack.push(origin.Big())
-
- case CALLER:
- caller := context.caller.Address()
- stack.push(common.Bytes2Big(caller.Bytes()))
-
- case CALLVALUE:
- stack.push(new(big.Int).Set(value))
-
- case CALLDATALOAD:
- data := getData(input, stack.pop(), common.Big32)
-
- stack.push(common.Bytes2Big(data))
- case CALLDATASIZE:
- l := int64(len(input))
- stack.push(big.NewInt(l))
-
- case CALLDATACOPY:
- var (
- mOff = stack.pop()
- cOff = stack.pop()
- l = stack.pop()
- )
- data := getData(input, cOff, l)
-
- mem.Set(mOff.Uint64(), l.Uint64(), data)
-
- case CODESIZE, EXTCODESIZE:
- var code []byte
- if op == EXTCODESIZE {
- addr := common.BigToAddress(stack.pop())
-
- code = statedb.GetCode(addr)
- } else {
- code = context.Code
- }
-
- l := big.NewInt(int64(len(code)))
- stack.push(l)
-
- case CODECOPY, EXTCODECOPY:
- var code []byte
- if op == EXTCODECOPY {
- addr := common.BigToAddress(stack.pop())
- code = statedb.GetCode(addr)
- } else {
- code = context.Code
- }
-
- var (
- mOff = stack.pop()
- cOff = stack.pop()
- l = stack.pop()
- )
-
- codeCopy := getData(code, cOff, l)
-
- mem.Set(mOff.Uint64(), l.Uint64(), codeCopy)
-
- case GASPRICE:
- stack.push(new(big.Int).Set(context.Price))
-
- case BLOCKHASH:
- num := stack.pop()
-
- n := new(big.Int).Sub(self.env.BlockNumber(), common.Big257)
- if num.Cmp(n) > 0 && num.Cmp(self.env.BlockNumber()) < 0 {
- stack.push(self.env.GetHash(num.Uint64()).Big())
+ if opPtr := jumpTable[op]; opPtr.valid {
+ if opPtr.fn != nil {
+ opPtr.fn(instruction{}, &pc, self.env, contract, mem, stack)
} else {
- stack.push(common.Big0)
- }
-
- case COINBASE:
- coinbase := self.env.Coinbase()
-
- stack.push(coinbase.Big())
-
- case TIMESTAMP:
- time := self.env.Time()
-
- stack.push(new(big.Int).Set(time))
-
- case NUMBER:
- number := self.env.BlockNumber()
-
- stack.push(U256(number))
-
- case DIFFICULTY:
- difficulty := self.env.Difficulty()
-
- stack.push(new(big.Int).Set(difficulty))
-
- case GASLIMIT:
-
- stack.push(new(big.Int).Set(self.env.GasLimit()))
-
- 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:
- size := uint64(op - PUSH1 + 1)
- byts := getData(code, new(big.Int).SetUint64(pc+1), new(big.Int).SetUint64(size))
- // push value to stack
- stack.push(common.Bytes2Big(byts))
- pc += size
-
- case POP:
- stack.pop()
- case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
- n := int(op - DUP1 + 1)
- stack.dup(n)
-
- case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
- n := int(op - SWAP1 + 2)
- stack.swap(n)
-
- case LOG0, LOG1, LOG2, LOG3, LOG4:
- n := int(op - LOG0)
- topics := make([]common.Hash, n)
- mStart, mSize := stack.pop(), stack.pop()
- for i := 0; i < n; i++ {
- topics[i] = common.BigToHash(stack.pop())
- }
-
- data := mem.Get(mStart.Int64(), mSize.Int64())
- log := state.NewLog(context.Address(), topics, data, self.env.BlockNumber().Uint64())
- self.env.AddLog(log)
-
- case MLOAD:
- offset := stack.pop()
- val := common.BigD(mem.Get(offset.Int64(), 32))
- stack.push(val)
-
- case MSTORE:
- // pop value of the stack
- mStart, val := stack.pop(), stack.pop()
- mem.Set(mStart.Uint64(), 32, common.BigToBytes(val, 256))
-
- case MSTORE8:
- off, val := stack.pop().Int64(), stack.pop().Int64()
-
- mem.store[off] = byte(val & 0xff)
-
- case SLOAD:
- loc := common.BigToHash(stack.pop())
- val := statedb.GetState(context.Address(), loc).Big()
- stack.push(val)
-
- case SSTORE:
- loc := common.BigToHash(stack.pop())
- val := stack.pop()
-
- statedb.SetState(context.Address(), loc, common.BigToHash(val))
-
- case JUMP:
- if err := jump(pc, stack.pop()); err != nil {
- return nil, err
- }
+ switch op {
+ case PC:
+ opPc(instruction{data: new(big.Int).SetUint64(pc)}, &pc, self.env, contract, mem, stack)
+ case JUMP:
+ if err := jump(pc, stack.pop()); err != nil {
+ return nil, err
+ }
- continue
- case JUMPI:
- pos, cond := stack.pop(), stack.pop()
+ continue
+ case JUMPI:
+ pos, cond := stack.pop(), stack.pop()
- if cond.Cmp(common.BigTrue) >= 0 {
- if err := jump(pc, pos); err != nil {
- return nil, err
- }
+ if cond.Cmp(common.BigTrue) >= 0 {
+ if err := jump(pc, pos); err != nil {
+ return nil, err
+ }
- continue
- }
+ continue
+ }
+ case RETURN:
+ offset, size := stack.pop(), stack.pop()
+ ret := mem.GetPtr(offset.Int64(), size.Int64())
- case JUMPDEST:
- case PC:
- stack.push(new(big.Int).SetUint64(pc))
- case MSIZE:
- stack.push(big.NewInt(int64(mem.Len())))
- case GAS:
- stack.push(new(big.Int).Set(context.Gas))
- case CREATE:
-
- var (
- value = stack.pop()
- offset, size = stack.pop(), stack.pop()
- input = mem.Get(offset.Int64(), size.Int64())
- gas = new(big.Int).Set(context.Gas)
- addr common.Address
- )
-
- context.UseGas(context.Gas)
- ret, suberr, ref := self.env.Create(context, input, gas, price, value)
- if suberr != nil {
- stack.push(common.BigFalse)
+ return contract.Return(ret), nil
+ case SUICIDE:
+ opSuicide(instruction{}, nil, self.env, contract, mem, stack)
- } else {
- // gas < len(ret) * CreateDataGas == NO_CODE
- dataGas := big.NewInt(int64(len(ret)))
- dataGas.Mul(dataGas, params.CreateDataGas)
- if context.UseGas(dataGas) {
- ref.SetCode(ret)
+ fallthrough
+ case STOP: // Stop the contract
+ return contract.Return(nil), nil
}
- addr = ref.Address()
-
- stack.push(addr.Big())
-
}
-
- case CALL, CALLCODE:
- gas := stack.pop()
- // pop gas and value of the stack.
- addr, value := stack.pop(), stack.pop()
- value = U256(value)
- // pop input size and offset
- inOffset, inSize := stack.pop(), stack.pop()
- // pop return size and offset
- retOffset, retSize := stack.pop(), stack.pop()
-
- address := common.BigToAddress(addr)
-
- // Get the arguments from the memory
- args := mem.Get(inOffset.Int64(), inSize.Int64())
-
- if len(value.Bytes()) > 0 {
- gas.Add(gas, params.CallStipend)
- }
-
- var (
- ret []byte
- err error
- )
- if op == CALLCODE {
- ret, err = self.env.CallCode(context, address, args, gas, price, value)
- } else {
- ret, err = self.env.Call(context, address, args, gas, price, value)
- }
-
- if err != nil {
- stack.push(common.BigFalse)
-
- } else {
- stack.push(common.BigTrue)
-
- mem.Set(retOffset.Uint64(), retSize.Uint64(), ret)
- }
-
- case RETURN:
- offset, size := stack.pop(), stack.pop()
- ret := mem.GetPtr(offset.Int64(), size.Int64())
-
- return context.Return(ret), nil
- case SUICIDE:
- receiver := statedb.GetOrNewStateObject(common.BigToAddress(stack.pop()))
- balance := statedb.GetBalance(context.Address())
-
- receiver.AddBalance(balance)
-
- statedb.Delete(context.Address())
-
- fallthrough
- case STOP: // Stop the context
-
- return context.Return(nil), nil
- default:
-
+ } else {
return nil, fmt.Errorf("Invalid opcode %x", op)
}
@@ -681,7 +225,7 @@ func (self *Vm) Run(context *Context, input []byte) (ret []byte, err error) {
// calculateGasAndSize calculates the required given the opcode and stack items calculates the new memorysize for
// the operation. This does not reduce gas or resizes the memory.
-func calculateGasAndSize(env Environment, context *Context, caller ContextRef, op OpCode, statedb *state.StateDB, mem *Memory, stack *stack) (*big.Int, *big.Int, error) {
+func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef, op OpCode, statedb Database, mem *Memory, stack *stack) (*big.Int, *big.Int, error) {
var (
gas = new(big.Int)
newMemSize *big.Int = new(big.Int)
@@ -731,7 +275,7 @@ func calculateGasAndSize(env Environment, context *Context, caller ContextRef, o
var g *big.Int
y, x := stack.data[stack.len()-2], stack.data[stack.len()-1]
- val := statedb.GetState(context.Address(), common.BigToHash(x))
+ val := statedb.GetState(contract.Address(), common.BigToHash(x))
// This checks for 3 scenario's and calculates gas accordingly
// 1. From a zero-value address to a non-zero value (NEW VALUE)
@@ -741,7 +285,7 @@ func calculateGasAndSize(env Environment, context *Context, caller ContextRef, o
// 0 => non 0
g = params.SstoreSetGas
} else if !common.EmptyHash(val) && common.EmptyHash(common.BigToHash(y)) {
- statedb.Refund(params.SstoreRefundGas)
+ statedb.AddRefund(params.SstoreRefundGas)
g = params.SstoreClearGas
} else {
@@ -750,8 +294,8 @@ func calculateGasAndSize(env Environment, context *Context, caller ContextRef, o
}
gas.Set(g)
case SUICIDE:
- if !statedb.IsDeleted(context.Address()) {
- statedb.Refund(params.SuicideRefundGas)
+ if !statedb.IsDeleted(contract.Address()) {
+ statedb.AddRefund(params.SuicideRefundGas)
}
case MLOAD:
newMemSize = calcMemSize(stack.peek(), u256(32))
@@ -788,7 +332,8 @@ func calculateGasAndSize(env Environment, context *Context, caller ContextRef, o
gas.Add(gas, stack.data[stack.len()-1])
if op == CALL {
- if env.State().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil {
+ //if env.Db().GetStateObject(common.BigToAddress(stack.data[stack.len()-2])) == nil {
+ if !env.Db().Exist(common.BigToAddress(stack.data[stack.len()-2])) {
gas.Add(gas, params.CallNewAccountGas)
}
}
@@ -802,38 +347,18 @@ func calculateGasAndSize(env Environment, context *Context, caller ContextRef, o
newMemSize = common.BigMax(x, y)
}
-
- if newMemSize.Cmp(common.Big0) > 0 {
- newMemSizeWords := toWordSize(newMemSize)
- newMemSize.Mul(newMemSizeWords, u256(32))
-
- if newMemSize.Cmp(u256(int64(mem.Len()))) > 0 {
- oldSize := toWordSize(big.NewInt(int64(mem.Len())))
- pow := new(big.Int).Exp(oldSize, common.Big2, Zero)
- linCoef := new(big.Int).Mul(oldSize, params.MemoryGas)
- quadCoef := new(big.Int).Div(pow, params.QuadCoeffDiv)
- oldTotalFee := new(big.Int).Add(linCoef, quadCoef)
-
- pow.Exp(newMemSizeWords, common.Big2, Zero)
- linCoef = new(big.Int).Mul(newMemSizeWords, params.MemoryGas)
- quadCoef = new(big.Int).Div(pow, params.QuadCoeffDiv)
- newTotalFee := new(big.Int).Add(linCoef, quadCoef)
-
- fee := new(big.Int).Sub(newTotalFee, oldTotalFee)
- gas.Add(gas, fee)
- }
- }
+ quadMemGas(mem, newMemSize, gas)
return newMemSize, gas, nil
}
// RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go
-func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, context *Context) (ret []byte, err error) {
+func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.Gas(len(input))
- if context.UseGas(gas) {
+ if contract.UseGas(gas) {
ret = p.Call(input)
- return context.Return(ret), nil
+ return contract.Return(ret), nil
} else {
return nil, OutOfGasError
}
@@ -841,19 +366,22 @@ func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, context *Con
// log emits a log event to the environment for each opcode encountered. This is not to be confused with the
// LOG* opcode.
-func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *stack, context *Context, err error) {
+func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *stack, contract *Contract, err error) {
if Debug {
mem := make([]byte, len(memory.Data()))
copy(mem, memory.Data())
- stck := make([]*big.Int, len(stack.Data()))
- copy(stck, stack.Data())
- object := context.self.(*state.StateObject)
+ stck := make([]*big.Int, len(stack.Data()))
+ for i, item := range stack.Data() {
+ stck[i] = new(big.Int).Set(item)
+ }
storage := make(map[common.Hash][]byte)
- object.EachStorage(func(k, v []byte) {
- storage[common.BytesToHash(k)] = v
- })
-
+ /*
+ object := contract.self.(*state.StateObject)
+ object.EachStorage(func(k, v []byte) {
+ storage[common.BytesToHash(k)] = v
+ })
+ */
self.env.AddStructLog(StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, err})
}
}
diff --git a/core/vm/vm_jit.go b/core/vm/vm_jit.go
index 339cb8ea8..07cb52d4a 100644
--- a/core/vm/vm_jit.go
+++ b/core/vm/vm_jit.go
@@ -30,6 +30,7 @@ void evmjit_destroy(void* _jit);
*/
import "C"
+/*
import (
"bytes"
"errors"
@@ -385,4 +386,4 @@ func env_extcode(_vm unsafe.Pointer, _addr unsafe.Pointer, o_size *uint64) *byte
code := vm.Env().State().GetCode(addr)
*o_size = uint64(len(code))
return getDataPtr(code)
-}
+}*/
diff --git a/core/vm_env.go b/core/vm_env.go
index a08f024fe..c8b50debc 100644
--- a/core/vm_env.go
+++ b/core/vm_env.go
@@ -30,13 +30,13 @@ type VMEnv struct {
header *types.Header
msg Message
depth int
- chain *ChainManager
+ chain *BlockChain
typ vm.Type
// structured logging
logs []vm.StructLog
}
-func NewEnv(state *state.StateDB, chain *ChainManager, msg Message, header *types.Header) *VMEnv {
+func NewEnv(state *state.StateDB, chain *BlockChain, msg Message, header *types.Header) *VMEnv {
return &VMEnv{
chain: chain,
state: state,
@@ -53,43 +53,49 @@ func (self *VMEnv) Time() *big.Int { return self.header.Time }
func (self *VMEnv) Difficulty() *big.Int { return self.header.Difficulty }
func (self *VMEnv) GasLimit() *big.Int { return self.header.GasLimit }
func (self *VMEnv) Value() *big.Int { return self.msg.Value() }
-func (self *VMEnv) State() *state.StateDB { return self.state }
+func (self *VMEnv) Db() vm.Database { return self.state }
func (self *VMEnv) Depth() int { return self.depth }
func (self *VMEnv) SetDepth(i int) { self.depth = i }
func (self *VMEnv) VmType() vm.Type { return self.typ }
func (self *VMEnv) SetVmType(t vm.Type) { self.typ = t }
func (self *VMEnv) GetHash(n uint64) common.Hash {
- if block := self.chain.GetBlockByNumber(n); block != nil {
- return block.Hash()
+ for block := self.chain.GetBlock(self.header.ParentHash); block != nil; block = self.chain.GetBlock(block.ParentHash()) {
+ if block.NumberU64() == n {
+ return block.Hash()
+ }
}
return common.Hash{}
}
-func (self *VMEnv) AddLog(log *state.Log) {
+func (self *VMEnv) AddLog(log *vm.Log) {
self.state.AddLog(log)
}
-func (self *VMEnv) CanTransfer(from vm.Account, balance *big.Int) bool {
- return from.Balance().Cmp(balance) >= 0
+func (self *VMEnv) CanTransfer(from common.Address, balance *big.Int) bool {
+ return self.state.GetBalance(from).Cmp(balance) >= 0
}
-func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) error {
- return vm.Transfer(from, to, amount)
+func (self *VMEnv) MakeSnapshot() vm.Database {
+ return self.state.Copy()
}
-func (self *VMEnv) Call(me vm.ContextRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- exe := NewExecution(self, &addr, data, gas, price, value)
- return exe.Call(addr, me)
+func (self *VMEnv) SetSnapshot(copy vm.Database) {
+ self.state.Set(copy.(*state.StateDB))
}
-func (self *VMEnv) CallCode(me vm.ContextRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
- maddr := me.Address()
- exe := NewExecution(self, &maddr, data, gas, price, value)
- return exe.Call(addr, me)
+
+func (self *VMEnv) Transfer(from, to vm.Account, amount *big.Int) {
+ Transfer(from, to, amount)
+}
+
+func (self *VMEnv) Call(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
+ return Call(self, me, addr, data, gas, price, value)
+}
+func (self *VMEnv) CallCode(me vm.ContractRef, addr common.Address, data []byte, gas, price, value *big.Int) ([]byte, error) {
+ return CallCode(self, me, addr, data, gas, price, value)
}
-func (self *VMEnv) Create(me vm.ContextRef, data []byte, gas, price, value *big.Int) ([]byte, error, vm.ContextRef) {
- exe := NewExecution(self, nil, data, gas, price, value)
- return exe.Create(me)
+func (self *VMEnv) Create(me vm.ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) {
+ return Create(self, me, data, gas, price, value)
}
func (self *VMEnv) StructLogs() []vm.StructLog {