diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/bench_test.go | 163 | ||||
-rw-r--r-- | core/block_cache_test.go | 6 | ||||
-rw-r--r-- | core/block_processor.go | 140 | ||||
-rw-r--r-- | core/block_processor_test.go | 17 | ||||
-rw-r--r-- | core/canary.go | 28 | ||||
-rw-r--r-- | core/chain_makers.go | 213 | ||||
-rw-r--r-- | core/chain_makers_test.go | 78 | ||||
-rw-r--r-- | core/chain_manager.go | 355 | ||||
-rw-r--r-- | core/chain_manager_test.go | 45 | ||||
-rw-r--r-- | core/genesis.go | 55 | ||||
-rw-r--r-- | core/state_transition.go | 66 | ||||
-rw-r--r-- | core/transaction_pool.go | 19 | ||||
-rw-r--r-- | core/transaction_pool_test.go | 91 | ||||
-rw-r--r-- | core/types/block.go | 438 | ||||
-rw-r--r-- | core/types/block_test.go | 19 | ||||
-rw-r--r-- | core/types/transaction.go | 265 | ||||
-rw-r--r-- | core/types/transaction_test.go | 32 | ||||
-rw-r--r-- | core/vm_env.go | 34 |
18 files changed, 1168 insertions, 896 deletions
diff --git a/core/bench_test.go b/core/bench_test.go new file mode 100644 index 000000000..6d851febd --- /dev/null +++ b/core/bench_test.go @@ -0,0 +1,163 @@ +package core + +import ( + "crypto/ecdsa" + "io/ioutil" + "math/big" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "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" +) + +func BenchmarkInsertChain_empty_memdb(b *testing.B) { + benchInsertChain(b, false, nil) +} +func BenchmarkInsertChain_empty_diskdb(b *testing.B) { + benchInsertChain(b, true, nil) +} +func BenchmarkInsertChain_valueTx_memdb(b *testing.B) { + benchInsertChain(b, false, genValueTx(0)) +} +func BenchmarkInsertChain_valueTx_diskdb(b *testing.B) { + benchInsertChain(b, true, genValueTx(0)) +} +func BenchmarkInsertChain_valueTx_100kB_memdb(b *testing.B) { + benchInsertChain(b, false, genValueTx(100*1024)) +} +func BenchmarkInsertChain_valueTx_100kB_diskdb(b *testing.B) { + benchInsertChain(b, true, genValueTx(100*1024)) +} +func BenchmarkInsertChain_uncles_memdb(b *testing.B) { + benchInsertChain(b, false, genUncles) +} +func BenchmarkInsertChain_uncles_diskdb(b *testing.B) { + benchInsertChain(b, true, genUncles) +} +func BenchmarkInsertChain_ring200_memdb(b *testing.B) { + benchInsertChain(b, false, genTxRing(200)) +} +func BenchmarkInsertChain_ring200_diskdb(b *testing.B) { + benchInsertChain(b, true, genTxRing(200)) +} +func BenchmarkInsertChain_ring1000_memdb(b *testing.B) { + benchInsertChain(b, false, genTxRing(1000)) +} +func BenchmarkInsertChain_ring1000_diskdb(b *testing.B) { + benchInsertChain(b, true, genTxRing(1000)) +} + +var ( + // This is the content of the genesis block used by the benchmarks. + benchRootKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + benchRootAddr = crypto.PubkeyToAddress(benchRootKey.PublicKey) + benchRootFunds = common.BigPow(2, 100) +) + +// genValueTx returns a block generator that includes a single +// value-transfer transaction with n bytes of extra data in each +// block. +func genValueTx(nbytes int) func(int, *BlockGen) { + return func(i int, gen *BlockGen) { + toaddr := common.Address{} + data := make([]byte, nbytes) + gas := IntrinsicGas(data) + tx, _ := types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data).SignECDSA(benchRootKey) + gen.AddTx(tx) + } +} + +var ( + ringKeys = make([]*ecdsa.PrivateKey, 1000) + ringAddrs = make([]common.Address, len(ringKeys)) +) + +func init() { + ringKeys[0] = benchRootKey + ringAddrs[0] = benchRootAddr + for i := 1; i < len(ringKeys); i++ { + ringKeys[i], _ = crypto.GenerateKey() + ringAddrs[i] = crypto.PubkeyToAddress(ringKeys[i].PublicKey) + } +} + +// genTxRing returns a block generator that sends ether in a ring +// among n accounts. This is creates n entries in the state database +// and fills the blocks with many small transactions. +func genTxRing(naccounts int) func(int, *BlockGen) { + from := 0 + return func(i int, gen *BlockGen) { + gas := CalcGasLimit(gen.PrevBlock(i - 1)) + for { + gas.Sub(gas, params.TxGas) + if gas.Cmp(params.TxGas) < 0 { + break + } + to := (from + 1) % naccounts + tx := types.NewTransaction( + gen.TxNonce(ringAddrs[from]), + ringAddrs[to], + benchRootFunds, + params.TxGas, + nil, + nil, + ) + tx, _ = tx.SignECDSA(ringKeys[from]) + gen.AddTx(tx) + from = to + } + } +} + +// genUncles generates blocks with two uncle headers. +func genUncles(i int, gen *BlockGen) { + if i >= 6 { + b2 := gen.PrevBlock(i - 6).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(i - 6).Header() + b3.Extra = []byte("bar") + gen.AddUncle(b3) + } +} + +func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { + // Create the database in memory or in a temporary directory. + var db common.Database + if !disk { + db, _ = ethdb.NewMemDatabase() + } else { + dir, err := ioutil.TempDir("", "eth-core-bench") + if err != nil { + b.Fatalf("cannot create temporary directory: %v", err) + } + defer os.RemoveAll(dir) + db, err = ethdb.NewLDBDatabase(dir) + if err != nil { + b.Fatalf("cannot create temporary database: %v", err) + } + defer db.Close() + } + + // Generate a chain of b.N blocks using the supplied block + // generator function. + genesis := GenesisBlockForTesting(db, benchRootAddr, benchRootFunds) + 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(genesis, db, db, FakePow{}, evmux) + chainman.SetProcessor(NewBlockProcessor(db, db, FakePow{}, chainman, evmux)) + defer chainman.Stop() + b.ReportAllocs() + b.ResetTimer() + if i, err := chainman.InsertChain(chain); err != nil { + b.Fatalf("insert error (block %d): %v\n", i, err) + } +} diff --git a/core/block_cache_test.go b/core/block_cache_test.go index 43ab847f9..80d118599 100644 --- a/core/block_cache_test.go +++ b/core/block_cache_test.go @@ -11,12 +11,12 @@ import ( func newChain(size int) (chain []*types.Block) { var parentHash common.Hash for i := 0; i < size; i++ { - block := types.NewBlock(parentHash, common.Address{}, common.Hash{}, new(big.Int), 0, nil) - block.Header().Number = big.NewInt(int64(i)) + head := &types.Header{ParentHash: parentHash, Number: big.NewInt(int64(i))} + block := types.NewBlock(head, nil, nil, nil) chain = append(chain, block) parentHash = block.Hash() } - return + return chain } func insertChainCache(cache *BlockCache, chain []*types.Block) { diff --git a/core/block_processor.go b/core/block_processor.go index e30ced312..22d4c7c27 100644 --- a/core/block_processor.go +++ b/core/block_processor.go @@ -57,8 +57,8 @@ func NewBlockProcessor(db, extra common.Database, pow pow.PoW, chainManager *Cha } func (sm *BlockProcessor) TransitionState(statedb *state.StateDB, parent, block *types.Block, transientProcess bool) (receipts types.Receipts, err error) { - coinbase := statedb.GetOrNewStateObject(block.Header().Coinbase) - coinbase.SetGasLimit(block.Header().GasLimit) + coinbase := statedb.GetOrNewStateObject(block.Coinbase()) + coinbase.SetGasLimit(block.GasLimit()) // Process the transactions on to parent state receipts, err = sm.ApplyTransactions(coinbase, statedb, block, block.Transactions(), transientProcess) @@ -69,11 +69,11 @@ func (sm *BlockProcessor) TransitionState(statedb *state.StateDB, parent, block return receipts, nil } -func (self *BlockProcessor) ApplyTransaction(coinbase *state.StateObject, statedb *state.StateDB, block *types.Block, tx *types.Transaction, usedGas *big.Int, transientProcess bool) (*types.Receipt, *big.Int, error) { +func (self *BlockProcessor) ApplyTransaction(coinbase *state.StateObject, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, transientProcess bool) (*types.Receipt, *big.Int, error) { // If we are mining this block and validating we want to set the logs back to 0 cb := statedb.GetStateObject(coinbase.Address()) - _, gas, err := ApplyMessage(NewEnv(statedb, self.bc, tx, block), tx, cb) + _, gas, err := ApplyMessage(NewEnv(statedb, self.bc, tx, header), tx, cb) if err != nil && (IsNonceErr(err) || state.IsGasLimitErr(err) || IsInvalidTxErr(err)) { return nil, nil, err } @@ -81,9 +81,8 @@ func (self *BlockProcessor) ApplyTransaction(coinbase *state.StateObject, stated // Update the state with pending changes statedb.Update() - cumulative := new(big.Int).Set(usedGas.Add(usedGas, gas)) - receipt := types.NewReceipt(statedb.Root().Bytes(), cumulative) - + usedGas.Add(usedGas, gas) + receipt := types.NewReceipt(statedb.Root().Bytes(), usedGas) logs := statedb.GetLogs(tx.Hash()) receipt.SetLogs(logs) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) @@ -108,12 +107,13 @@ func (self *BlockProcessor) ApplyTransactions(coinbase *state.StateObject, state totalUsedGas = big.NewInt(0) err error cumulativeSum = new(big.Int) + header = block.Header() ) for i, tx := range txs { statedb.StartRecord(tx.Hash(), block.Hash(), i) - receipt, txGas, err := self.ApplyTransaction(coinbase, statedb, block, tx, totalUsedGas, transientProcess) + receipt, txGas, err := self.ApplyTransaction(coinbase, statedb, header, tx, totalUsedGas, transientProcess) if err != nil && (IsNonceErr(err) || state.IsGasLimitErr(err) || IsInvalidTxErr(err)) { return nil, err } @@ -142,11 +142,10 @@ func (sm *BlockProcessor) RetryProcess(block *types.Block) (logs state.Logs, err sm.mutex.Lock() defer sm.mutex.Unlock() - header := block.Header() - if !sm.bc.HasBlock(header.ParentHash) { - return nil, ParentError(header.ParentHash) + if !sm.bc.HasBlock(block.ParentHash()) { + return nil, ParentError(block.ParentHash()) } - parent := sm.bc.GetBlock(header.ParentHash) + parent := sm.bc.GetBlock(block.ParentHash()) // FIXME Change to full header validation. See #1225 errch := make(chan bool) @@ -168,30 +167,32 @@ func (sm *BlockProcessor) Process(block *types.Block) (logs state.Logs, err erro sm.mutex.Lock() defer sm.mutex.Unlock() - header := block.Header() - if sm.bc.HasBlock(header.Hash()) { - return nil, &KnownBlockError{header.Number, header.Hash()} + if sm.bc.HasBlock(block.Hash()) { + return nil, &KnownBlockError{block.Number(), block.Hash()} } - if !sm.bc.HasBlock(header.ParentHash) { - return nil, ParentError(header.ParentHash) + if !sm.bc.HasBlock(block.ParentHash()) { + return nil, ParentError(block.ParentHash()) } - parent := sm.bc.GetBlock(header.ParentHash) + parent := sm.bc.GetBlock(block.ParentHash()) return sm.processWithParent(block, parent) } func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs state.Logs, err error) { // Create a new state based on the parent's root (e.g., create copy) state := state.New(parent.Root(), sm.db) + header := block.Header() + uncles := block.Uncles() + txs := block.Transactions() // Block validation - if err = ValidateHeader(sm.Pow, block.Header(), parent.Header(), false); err != nil { + if err = ValidateHeader(sm.Pow, header, parent, false); err != nil { return } // There can be at most two uncles - if len(block.Uncles()) > 2 { - return nil, ValidationError("Block can only contain maximum 2 uncles (contained %v)", len(block.Uncles())) + if len(uncles) > 2 { + return nil, ValidationError("Block can only contain maximum 2 uncles (contained %v)", len(uncles)) } receipts, err := sm.TransitionState(state, parent, block, false) @@ -199,8 +200,6 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs st return } - header := block.Header() - // Validate the received block's bloom with the one derived from the generated receipts. // For valid blocks this should always validate to true. rbloom := types.CreateBloom(receipts) @@ -211,7 +210,7 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs st // The transactions Trie's root (R = (Tr [[i, RLP(T1)], [i, RLP(T2)], ... [n, RLP(Tn)]])) // can be used by light clients to make sure they've received the correct Txs - txSha := types.DeriveSha(block.Transactions()) + txSha := types.DeriveSha(txs) if txSha != header.TxHash { err = fmt.Errorf("invalid transaction root hash. received=%x calculated=%x", header.TxHash, txSha) return @@ -225,7 +224,7 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs st } // Verify UncleHash before running other uncle validations - unclesSha := block.CalculateUnclesHash() + unclesSha := types.CalcUncleHash(uncles) if unclesSha != header.UncleHash { err = fmt.Errorf("invalid uncles root hash. received=%x calculated=%x", header.UncleHash, unclesSha) return @@ -236,7 +235,7 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs st return } // Accumulate static rewards; block reward, uncle's and uncle inclusion. - AccumulateRewards(state, block) + AccumulateRewards(state, header, uncles) // Commit state objects/accounts to a temporary trie (does not save) // used to calculate the state root. @@ -260,20 +259,44 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs st return state.Logs(), nil } +var ( + big8 = big.NewInt(8) + big32 = big.NewInt(32) +) + +// AccumulateRewards credits the coinbase of the given block with the +// mining reward. The total reward consists of the static block reward +// and rewards for included uncles. The coinbase of each uncle block is +// also rewarded. +func AccumulateRewards(statedb *state.StateDB, header *types.Header, uncles []*types.Header) { + reward := new(big.Int).Set(BlockReward) + r := new(big.Int) + for _, uncle := range uncles { + r.Add(uncle.Number, big8) + r.Sub(r, header.Number) + r.Mul(r, BlockReward) + r.Div(r, big8) + statedb.AddBalance(uncle.Coinbase, r) + + r.Div(BlockReward, big32) + reward.Add(reward, r) + } + statedb.AddBalance(header.Coinbase, reward) +} + func (sm *BlockProcessor) VerifyUncles(statedb *state.StateDB, block, parent *types.Block) error { - ancestors := set.New() uncles := set.New() - ancestorHeaders := make(map[common.Hash]*types.Header) - for _, ancestor := range sm.bc.GetAncestors(block, 7) { - ancestorHeaders[ancestor.Hash()] = ancestor.Header() - ancestors.Add(ancestor.Hash()) + ancestors := make(map[common.Hash]*types.Block) + for _, ancestor := range sm.bc.GetBlocksFromHash(block.ParentHash(), 7) { + ancestors[ancestor.Hash()] = ancestor // Include ancestors uncles in the uncle set. Uncles must be unique. for _, uncle := range ancestor.Uncles() { uncles.Add(uncle.Hash()) } } - + ancestors[block.Hash()] = block uncles.Add(block.Hash()) + for i, uncle := range block.Uncles() { hash := uncle.Hash() if uncles.Has(hash) { @@ -282,22 +305,20 @@ func (sm *BlockProcessor) VerifyUncles(statedb *state.StateDB, block, parent *ty } uncles.Add(hash) - if ancestors.Has(hash) { + if ancestors[hash] != nil { branch := fmt.Sprintf(" O - %x\n |\n", block.Hash()) - ancestors.Each(func(item interface{}) bool { - branch += fmt.Sprintf(" O - %x\n |\n", hash) - return true - }) + for h := range ancestors { + branch += fmt.Sprintf(" O - %x\n |\n", h) + } glog.Infoln(branch) - return UncleError("uncle[%d](%x) is ancestor", i, hash[:4]) } - if !ancestors.Has(uncle.ParentHash) || uncle.ParentHash == parent.Hash() { + if ancestors[uncle.ParentHash] == nil || uncle.ParentHash == parent.Hash() { return UncleError("uncle[%d](%x)'s parent is not ancestor (%x)", i, hash[:4], uncle.ParentHash[0:4]) } - if err := ValidateHeader(sm.Pow, uncle, ancestorHeaders[uncle.ParentHash], true); err != nil { + if err := ValidateHeader(sm.Pow, uncle, ancestors[uncle.ParentHash], true); err != nil { return ValidationError(fmt.Sprintf("uncle[%d](%x) header invalid: %v", i, hash[:4], err)) } } @@ -325,7 +346,7 @@ func (sm *BlockProcessor) GetLogs(block *types.Block) (logs state.Logs, err erro // TODO: remove backward compatibility var ( - parent = sm.bc.GetBlock(block.Header().ParentHash) + parent = sm.bc.GetBlock(block.ParentHash()) state = state.New(parent.Root(), sm.db) ) @@ -336,19 +357,22 @@ func (sm *BlockProcessor) GetLogs(block *types.Block) (logs state.Logs, err erro // See YP section 4.3.4. "Block Header Validity" // Validates a block. Returns an error if the block is invalid. -func ValidateHeader(pow pow.PoW, block, parent *types.Header, checkPow bool) error { +func ValidateHeader(pow pow.PoW, block *types.Header, parent *types.Block, checkPow bool) error { if big.NewInt(int64(len(block.Extra))).Cmp(params.MaximumExtraDataSize) == 1 { return fmt.Errorf("Block extra data too long (%d)", len(block.Extra)) } - expd := CalcDifficulty(block, parent) + expd := CalcDifficulty(int64(block.Time), int64(parent.Time()), parent.Difficulty()) if expd.Cmp(block.Difficulty) != 0 { return fmt.Errorf("Difficulty check failed for block %v, %v", block.Difficulty, expd) } - a := new(big.Int).Sub(block.GasLimit, parent.GasLimit) + var a, b *big.Int + a = parent.GasLimit() + a = a.Sub(a, block.GasLimit) a.Abs(a) - b := new(big.Int).Div(parent.GasLimit, params.GasLimitBoundDivisor) + b = parent.GasLimit() + b = b.Div(b, params.GasLimitBoundDivisor) if !(a.Cmp(b) < 0) || (block.GasLimit.Cmp(params.MinGasLimit) == -1) { return fmt.Errorf("GasLimit check failed for block %v (%v > %v)", block.GasLimit, a, b) } @@ -357,11 +381,13 @@ func ValidateHeader(pow pow.PoW, block, parent *types.Header, checkPow bool) err return BlockFutureErr } - if new(big.Int).Sub(block.Number, parent.Number).Cmp(big.NewInt(1)) != 0 { + num := parent.Number() + num.Sub(block.Number, num) + if num.Cmp(big.NewInt(1)) != 0 { return BlockNumberErr } - if block.Time <= parent.Time { + if block.Time <= uint64(parent.Time()) { return BlockEqualTSErr //ValidationError("Block timestamp equal or less than previous block (%v - %v)", block.Time, parent.Time) } @@ -375,26 +401,6 @@ func ValidateHeader(pow pow.PoW, block, parent *types.Header, checkPow bool) err return nil } -func AccumulateRewards(statedb *state.StateDB, block *types.Block) { - reward := new(big.Int).Set(BlockReward) - - for _, uncle := range block.Uncles() { - num := new(big.Int).Add(big.NewInt(8), uncle.Number) - num.Sub(num, block.Number()) - - r := new(big.Int) - r.Mul(BlockReward, num) - r.Div(r, big.NewInt(8)) - - statedb.AddBalance(uncle.Coinbase, r) - - reward.Add(reward, new(big.Int).Div(BlockReward, big.NewInt(32))) - } - - // Get the account associated with the coinbase - statedb.AddBalance(block.Header().Coinbase, reward) -} - func getBlockReceipts(db common.Database, bhash common.Hash) (receipts types.Receipts, err error) { var rdata []byte rdata, err = db.Get(append(receiptsPre, bhash[:]...)) diff --git a/core/block_processor_test.go b/core/block_processor_test.go index e38c815ef..dc328a3ea 100644 --- a/core/block_processor_test.go +++ b/core/block_processor_test.go @@ -26,20 +26,19 @@ func proc() (*BlockProcessor, *ChainManager) { } func TestNumber(t *testing.T) { - _, chain := proc() - block1 := chain.NewBlock(common.Address{}) - block1.Header().Number = big.NewInt(3) - block1.Header().Time-- - pow := ezp.New() + _, chain := proc() - err := ValidateHeader(pow, block1.Header(), chain.Genesis().Header(), false) + statedb := state.New(chain.Genesis().Root(), chain.stateDb) + header := makeHeader(chain.Genesis(), statedb) + header.Number = big.NewInt(3) + err := ValidateHeader(pow, header, chain.Genesis(), false) if err != BlockNumberErr { - t.Errorf("expected block number error %v", err) + t.Errorf("expected block number error, got %q", err) } - block1 = chain.NewBlock(common.Address{}) - err = ValidateHeader(pow, block1.Header(), chain.Genesis().Header(), false) + header = makeHeader(chain.Genesis(), statedb) + err = ValidateHeader(pow, header, chain.Genesis(), false) if err == BlockNumberErr { t.Errorf("didn't expect block number error") } diff --git a/core/canary.go b/core/canary.go new file mode 100644 index 000000000..de77c4bba --- /dev/null +++ b/core/canary.go @@ -0,0 +1,28 @@ +package core + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" +) + +var ( + jeff = common.HexToAddress("9d38997c624a71b21278389ea2fdc460d000e4b2") + vitalik = common.HexToAddress("b1e570be07eaa673e4fd0c8265b64ef739385709") + christoph = common.HexToAddress("529bc43a5d93789fa28de1961db6a07e752204ae") + gav = common.HexToAddress("e3e942b2aa524293c84ff6c7f87a6635790ad5e4") +) + +// Canary will check the 0'd address of the 4 contracts above. +// If two or more are set to anything other than a 0 the canary +// dies a horrible death. +func Canary(statedb *state.StateDB) bool { + r := new(big.Int) + r.Add(r, statedb.GetState(jeff, common.Hash{}).Big()) + r.Add(r, statedb.GetState(vitalik, common.Hash{}).Big()) + r.Add(r, statedb.GetState(christoph, common.Hash{}).Big()) + r.Add(r, statedb.GetState(gav, common.Hash{}).Big()) + + return r.Cmp(big.NewInt(1)) > 0 +} diff --git a/core/chain_makers.go b/core/chain_makers.go index 76acfd6ca..72ae7970e 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -1,7 +1,6 @@ package core import ( - "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -11,7 +10,8 @@ import ( "github.com/ethereum/go-ethereum/pow" ) -// So we can generate blocks easily +// FakePow is a non-validating proof of work implementation. +// It returns true from Verify for any block. type FakePow struct{} func (f FakePow) Search(block pow.Block, stop <-chan struct{}) (uint64, []byte) { @@ -23,81 +23,125 @@ func (f FakePow) Turbo(bool) {} // So we can deterministically seed different blockchains var ( - CanonicalSeed = 1 - ForkSeed = 2 + canonicalSeed = 1 + forkSeed = 2 ) -// Utility functions for making chains on the fly -// Exposed for sake of testing from other packages (eg. go-ethash) -func NewBlockFromParent(addr common.Address, parent *types.Block) *types.Block { - return newBlockFromParent(addr, parent) +// BlockGen creates blocks for testing. +// See GenerateChain for a detailed explanation. +type BlockGen struct { + i int + parent *types.Block + chain []*types.Block + header *types.Header + statedb *state.StateDB + + coinbase *state.StateObject + txs []*types.Transaction + receipts []*types.Receipt + uncles []*types.Header } -func MakeBlock(bman *BlockProcessor, parent *types.Block, i int, db common.Database, seed int) *types.Block { - return makeBlock(bman, parent, i, db, seed) -} - -func MakeChain(bman *BlockProcessor, parent *types.Block, max int, db common.Database, seed int) types.Blocks { - return makeChain(bman, parent, max, db, seed) +// 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 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) } -func NewChainMan(block *types.Block, eventMux *event.TypeMux, db common.Database) *ChainManager { - return newChainManager(block, eventMux, db) +// SetExtra sets the extra data field of the generated block. +func (b *BlockGen) SetExtra(data []byte) { + b.header.Extra = data } -func NewBlockProc(db common.Database, cman *ChainManager, eventMux *event.TypeMux) *BlockProcessor { - return newBlockProcessor(db, cman, eventMux) +// AddTx adds a transaction to the generated block. If no coinbase has +// been set, the block's coinbase is set to the zero address. +// +// AddTx panics if the transaction cannot be executed. In addition to +// the protocol-imposed limitations (gas limit, etc.), there are some +// further limitations on the content of transactions that can be +// added. Notably, contract code relying on the BLOCKHASH instruction +// will panic during execution. +func (b *BlockGen) AddTx(tx *types.Transaction) { + if b.coinbase == nil { + b.SetCoinbase(common.Address{}) + } + _, gas, err := ApplyMessage(NewEnv(b.statedb, nil, tx, b.header), tx, b.coinbase) + if err != nil { + panic(err) + } + b.statedb.Update() + b.header.GasUsed.Add(b.header.GasUsed, gas) + receipt := types.NewReceipt(b.statedb.Root().Bytes(), b.header.GasUsed) + logs := b.statedb.GetLogs(tx.Hash()) + receipt.SetLogs(logs) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + b.txs = append(b.txs, tx) + b.receipts = append(b.receipts, receipt) } -func NewCanonical(n int, db common.Database) (*BlockProcessor, error) { - return newCanonical(n, db) +// 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 { + if !b.statedb.HasAccount(addr) { + panic("account does not exist") + } + return b.statedb.GetNonce(addr) } -// block time is fixed at 10 seconds -func newBlockFromParent(addr common.Address, parent *types.Block) *types.Block { - block := types.NewBlock(parent.Hash(), addr, parent.Root(), common.BigPow(2, 32), 0, nil) - block.SetUncles(nil) - block.SetTransactions(nil) - block.SetReceipts(nil) - - header := block.Header() - header.Difficulty = CalcDifficulty(block.Header(), parent.Header()) - header.Number = new(big.Int).Add(parent.Header().Number, common.Big1) - header.Time = parent.Header().Time + 10 - header.GasLimit = CalcGasLimit(parent) - - block.Td = parent.Td - - return block +// AddUncle adds an uncle header to the generated block. +func (b *BlockGen) AddUncle(h *types.Header) { + b.uncles = append(b.uncles, h) } -// Actually make a block by simulating what miner would do -// we seed chains by the first byte of the coinbase -func makeBlock(bman *BlockProcessor, parent *types.Block, i int, db common.Database, seed int) *types.Block { - var addr common.Address - addr[0], addr[19] = byte(seed), byte(i) - block := newBlockFromParent(addr, parent) - state := state.New(block.Root(), db) - cbase := state.GetOrNewStateObject(addr) - cbase.SetGasLimit(CalcGasLimit(parent)) - cbase.AddBalance(BlockReward) - state.Update() - block.SetRoot(state.Root()) - return block +// PrevBlock returns a previously generated block by number. It panics if +// num is greater or equal to the number of the block being generated. +// For index -1, PrevBlock returns the parent block given to GenerateChain. +func (b *BlockGen) PrevBlock(index int) *types.Block { + if index >= b.i { + panic("block index out of range") + } + if index == -1 { + return b.parent + } + return b.chain[index] } -// Make a chain with real blocks -// Runs ProcessWithParent to get proper state roots -func makeChain(bman *BlockProcessor, parent *types.Block, max int, db common.Database, seed int) types.Blocks { - bman.bc.currentBlock = parent - blocks := make(types.Blocks, max) - for i := 0; i < max; i++ { - block := makeBlock(bman, parent, i, db, seed) - _, err := bman.processWithParent(block, parent) - if err != nil { - fmt.Println("process with parent failed", err) - panic(err) +// GenerateChain creates a chain of n blocks. The first block's +// parent will be the provided parent. db is used to store +// intermediate states and should contain the parent's state trie. +// +// The generator function is called with a new block generator for +// every block. Any transactions and uncles added to the generator +// become part of the block. If gen is nil, the blocks will be empty +// 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 +// a similar non-validating proof of work implementation. +func GenerateChain(parent *types.Block, db common.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 { + b := &BlockGen{parent: parent, i: i, chain: blocks, header: h, statedb: statedb} + if gen != nil { + gen(i, b) } + AccumulateRewards(statedb, h, b.uncles) + statedb.Update() + h.Root = statedb.Root() + return types.NewBlock(h, b.txs, b.uncles, b.receipts) + } + for i := 0; i < n; i++ { + header := makeHeader(parent, statedb) + block := genblock(i, header) block.Td = CalcTD(block, parent) blocks[i] = block parent = block @@ -105,41 +149,38 @@ func makeChain(bman *BlockProcessor, parent *types.Block, max int, db common.Dat return blocks } -// Create a new chain manager starting from given block -// Effectively a fork factory -func newChainManager(block *types.Block, eventMux *event.TypeMux, db common.Database) *ChainManager { - genesis := GenesisBlock(0, db) - bc := &ChainManager{blockDb: db, stateDb: db, genesisBlock: genesis, eventMux: eventMux, pow: FakePow{}} - bc.txState = state.ManageState(state.New(genesis.Root(), db)) - bc.futureBlocks = NewBlockCache(1000) - if block == nil { - bc.Reset() - } else { - bc.currentBlock = block - bc.td = block.Td +func makeHeader(parent *types.Block, state *state.StateDB) *types.Header { + time := parent.Time() + 10 // block time is fixed at 10 seconds + return &types.Header{ + Root: state.Root(), + ParentHash: parent.Hash(), + Coinbase: parent.Coinbase(), + Difficulty: CalcDifficulty(time, parent.Time(), parent.Difficulty()), + GasLimit: CalcGasLimit(parent), + GasUsed: new(big.Int), + Number: new(big.Int).Add(parent.Number(), common.Big1), + Time: uint64(time), } - return bc } -// block processor with fake pow -func newBlockProcessor(db common.Database, cman *ChainManager, eventMux *event.TypeMux) *BlockProcessor { - chainMan := newChainManager(nil, eventMux, db) - bman := NewBlockProcessor(db, db, FakePow{}, chainMan, eventMux) - return bman -} - -// Make a new, deterministic canonical chain by running InsertChain -// on result of makeChain +// newCanonical creates a new deterministic canonical chain by running +// InsertChain on the result of makeChain. func newCanonical(n int, db common.Database) (*BlockProcessor, error) { - eventMux := &event.TypeMux{} - - bman := newBlockProcessor(db, newChainManager(nil, eventMux, db), eventMux) + evmux := &event.TypeMux{} + chainman, _ := NewChainManager(GenesisBlock(0, db), db, db, FakePow{}, evmux) + bman := NewBlockProcessor(db, db, FakePow{}, chainman, evmux) bman.bc.SetProcessor(bman) parent := bman.bc.CurrentBlock() if n == 0 { return bman, nil } - lchain := makeChain(bman, parent, n, db, CanonicalSeed) + lchain := makeChain(parent, n, db, canonicalSeed) _, err := bman.bc.InsertChain(lchain) return bman, err } + +func makeChain(parent *types.Block, n int, db common.Database, seed int) []*types.Block { + return GenerateChain(parent, db, n, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) + }) +} diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go new file mode 100644 index 000000000..d5125e1c3 --- /dev/null +++ b/core/chain_makers_test.go @@ -0,0 +1,78 @@ +package core + +import ( + "fmt" + "math/big" + + "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" +) + +func ExampleGenerateChain() { + 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() + ) + + // Ensure that key1 has some funds in the genesis block. + genesis := GenesisBlockForTesting(db, addr1, big.NewInt(1000000)) + + // 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) { + switch i { + case 0: + // In block 1, addr1 sends addr2 some ether. + tx, _ := types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil).SignECDSA(key1) + gen.AddTx(tx) + case 1: + // In block 2, addr1 sends some more ether to addr2. + // addr2 passes it on to addr3. + tx1, _ := types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1) + tx2, _ := types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2) + gen.AddTx(tx1) + gen.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by addr3. + gen.SetCoinbase(addr3) + gen.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := gen.PrevBlock(1).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(2).Header() + b3.Extra = []byte("foo") + gen.AddUncle(b3) + } + }) + + // Import the chain. This runs all block validation rules. + evmux := &event.TypeMux{} + chainman, _ := NewChainManager(genesis, db, db, FakePow{}, evmux) + chainman.SetProcessor(NewBlockProcessor(db, 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() + 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)) + fmt.Println("balance of addr3:", state.GetBalance(addr3)) + // Output: + // last block: #5 + // balance of addr1: 989000 + // balance of addr2: 10000 + // balance of addr3: 5906250000000001000 +} diff --git a/core/chain_manager.go b/core/chain_manager.go index 3b9b7517b..fc1922b3b 100644 --- a/core/chain_manager.go +++ b/core/chain_manager.go @@ -11,15 +11,19 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/compression/rle" "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/params" "github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/rlp" + "github.com/hashicorp/golang-lru" "github.com/rcrowley/go-metrics" + "github.com/syndtr/goleveldb/leveldb" ) var ( @@ -33,35 +37,40 @@ var ( ) const ( - blockCacheLimit = 10000 + blockCacheLimit = 256 maxFutureBlocks = 256 maxTimeFutureBlocks = 30 ) -func CalcDifficulty(block, parent *types.Header) *big.Int { +// CalcDifficulty is the difficulty adjustment algorithm. It returns +// the difficulty that a new block b should have when created at time +// given the parent block's time and difficulty. +func CalcDifficulty(time int64, parentTime int64, parentDiff *big.Int) *big.Int { diff := new(big.Int) - - adjust := new(big.Int).Div(parent.Difficulty, params.DifficultyBoundDivisor) - if big.NewInt(int64(block.Time)-int64(parent.Time)).Cmp(params.DurationLimit) < 0 { - diff.Add(parent.Difficulty, adjust) + adjust := new(big.Int).Div(parentDiff, params.DifficultyBoundDivisor) + if big.NewInt(time-parentTime).Cmp(params.DurationLimit) < 0 { + diff.Add(parentDiff, adjust) } else { - diff.Sub(parent.Difficulty, adjust) + diff.Sub(parentDiff, adjust) } - if diff.Cmp(params.MinimumDifficulty) < 0 { return params.MinimumDifficulty } - return diff } +// CalcTD computes the total difficulty of block. func CalcTD(block, parent *types.Block) *big.Int { if parent == nil { return block.Difficulty() } - return new(big.Int).Add(parent.Td, block.Header().Difficulty) + d := block.Difficulty() + d.Add(d, parent.Td) + return d } +// CalcGasLimit computes the gas limit of the next block after parent. +// The result may be modified by the caller. func CalcGasLimit(parent *types.Block) *big.Int { decay := new(big.Int).Div(parent.GasLimit(), params.GasLimitBoundDivisor) contrib := new(big.Int).Mul(parent.GasUsed(), big.NewInt(3)) @@ -71,11 +80,11 @@ func CalcGasLimit(parent *types.Block) *big.Int { gl := new(big.Int).Sub(parent.GasLimit(), decay) gl = gl.Add(gl, contrib) gl = gl.Add(gl, big.NewInt(1)) - gl = common.BigMax(gl, params.MinGasLimit) + gl.Set(common.BigMax(gl, params.MinGasLimit)) if gl.Cmp(params.GenesisGasLimit) < 0 { - gl2 := new(big.Int).Add(parent.GasLimit(), decay) - return common.BigMin(params.GenesisGasLimit, gl2) + gl.Add(parent.GasLimit(), decay) + gl.Set(common.BigMin(gl, params.GenesisGasLimit)) } return gl } @@ -100,8 +109,9 @@ type ChainManager struct { transState *state.StateDB txState *state.ManagedState - cache *BlockCache - futureBlocks *BlockCache + cache *lru.Cache // cache is the LRU caching + futureBlocks *lru.Cache // future blocks are blocks added for later processing + pendingBlocks *lru.Cache // pending blocks contain blocks not yet written to the db quit chan struct{} // procInterrupt must be atomically called @@ -112,13 +122,14 @@ type ChainManager struct { } func NewChainManager(genesis *types.Block, blockDb, stateDb common.Database, pow pow.PoW, mux *event.TypeMux) (*ChainManager, error) { + cache, _ := lru.New(blockCacheLimit) bc := &ChainManager{ blockDb: blockDb, stateDb: stateDb, genesisBlock: GenesisBlock(42, stateDb), eventMux: mux, quit: make(chan struct{}), - cache: NewBlockCache(blockCacheLimit), + cache: cache, pow: pow, } // Check the genesis block given to the chain manager. If the genesis block mismatches block number 0 @@ -147,7 +158,7 @@ func NewChainManager(genesis *types.Block, blockDb, stateDb common.Database, pow // Take ownership of this particular state bc.txState = state.ManageState(bc.State().Copy()) - bc.futureBlocks = NewBlockCache(maxFutureBlocks) + bc.futureBlocks, _ = lru.New(maxFutureBlocks) bc.makeCache() go bc.update() @@ -159,11 +170,11 @@ 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.Header().ParentHash) { + for block := bc.currentBlock; block != nil && block.Hash() != head.Hash(); block = bc.GetBlock(block.ParentHash()) { bc.removeBlock(block) } - bc.cache = NewBlockCache(blockCacheLimit) + bc.cache, _ = lru.New(blockCacheLimit) bc.currentBlock = head bc.makeCache() @@ -251,65 +262,23 @@ func (bc *ChainManager) setLastState() { } func (bc *ChainManager) makeCache() { - if bc.cache == nil { - bc.cache = NewBlockCache(blockCacheLimit) - } + bc.cache, _ = lru.New(blockCacheLimit) // load in last `blockCacheLimit` - 1 blocks. Last block is the current. - ancestors := bc.GetAncestors(bc.currentBlock, blockCacheLimit-1) - ancestors = append(ancestors, bc.currentBlock) - for _, block := range ancestors { - bc.cache.Push(block) + bc.cache.Add(bc.genesisBlock.Hash(), bc.genesisBlock) + for _, block := range bc.GetBlocksFromHash(bc.currentBlock.Hash(), blockCacheLimit) { + bc.cache.Add(block.Hash(), block) } } -// Block creation & chain handling -func (bc *ChainManager) NewBlock(coinbase common.Address) *types.Block { - bc.mu.RLock() - defer bc.mu.RUnlock() - - var ( - root common.Hash - parentHash common.Hash - ) - - if bc.currentBlock != nil { - root = bc.currentBlock.Header().Root - parentHash = bc.lastBlockHash - } - - block := types.NewBlock( - parentHash, - coinbase, - root, - common.BigPow(2, 32), - 0, - nil) - block.SetUncles(nil) - block.SetTransactions(nil) - block.SetReceipts(nil) - - parent := bc.currentBlock - if parent != nil { - header := block.Header() - header.Difficulty = CalcDifficulty(block.Header(), parent.Header()) - header.Number = new(big.Int).Add(parent.Header().Number, common.Big1) - header.GasLimit = CalcGasLimit(parent) - } - - return block -} - func (bc *ChainManager) Reset() { bc.mu.Lock() defer bc.mu.Unlock() - for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.Header().ParentHash) { + for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.ParentHash()) { bc.removeBlock(block) } - if bc.cache == nil { - bc.cache = NewBlockCache(blockCacheLimit) - } + bc.cache, _ = lru.New(blockCacheLimit) // Prepare the genesis block bc.write(bc.genesisBlock) @@ -328,7 +297,7 @@ func (bc *ChainManager) ResetWithGenesisBlock(gb *types.Block) { bc.mu.Lock() defer bc.mu.Unlock() - for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.Header().ParentHash) { + for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.ParentHash()) { bc.removeBlock(block) } @@ -393,15 +362,20 @@ func (bc *ChainManager) insert(block *types.Block) { } func (bc *ChainManager) write(block *types.Block) { - enc, _ := rlp.EncodeToBytes((*types.StorageBlock)(block)) - key := append(blockHashPre, block.Hash().Bytes()...) - err := bc.blockDb.Put(key, enc) - if err != nil { - glog.Fatal("db write fail:", err) - } + tstart := time.Now() + + go func() { + enc, _ := rlp.EncodeToBytes((*types.StorageBlock)(block)) + key := append(blockHashPre, block.Hash().Bytes()...) + err := bc.blockDb.Put(key, enc) + if err != nil { + glog.Fatal("db write fail:", err) + } + }() - // Push block to cache - bc.cache.Push(block) + if glog.V(logger.Debug) { + glog.Infof("wrote block #%v %s. Took %v\n", block.Number(), common.PP(block.Hash().Bytes()), time.Since(tstart)) + } } // Accessors @@ -411,6 +385,16 @@ func (bc *ChainManager) Genesis() *types.Block { // Block fetching methods func (bc *ChainManager) HasBlock(hash common.Hash) bool { + if bc.cache.Contains(hash) { + return true + } + + if bc.pendingBlocks != nil { + if _, exist := bc.pendingBlocks.Get(hash); exist { + return true + } + } + data, _ := bc.blockDb.Get(append(blockHashPre, hash[:]...)) return len(data) != 0 } @@ -437,11 +421,15 @@ func (self *ChainManager) GetBlockHashesFromHash(hash common.Hash, max uint64) ( } func (self *ChainManager) GetBlock(hash common.Hash) *types.Block { - /* - if block := self.cache.Get(hash); block != nil { - return block + if block, ok := self.cache.Get(hash); ok { + return block.(*types.Block) + } + + if self.pendingBlocks != nil { + if block, _ := self.pendingBlocks.Get(hash); block != nil { + return block.(*types.Block) } - */ + } data, _ := self.blockDb.Get(append(blockHashPre, hash[:]...)) if len(data) == 0 { @@ -452,6 +440,10 @@ func (self *ChainManager) GetBlock(hash common.Hash) *types.Block { glog.V(logger.Error).Infof("invalid block RLP for hash %x: %v", hash, err) return nil } + + // Add the block to the cache + self.cache.Add(hash, (*types.Block)(&block)) + return (*types.Block)(&block) } @@ -463,6 +455,19 @@ func (self *ChainManager) GetBlockByNumber(num uint64) *types.Block { } +// 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 +} + // non blocking version func (self *ChainManager) getBlockByNumber(num uint64) *types.Block { key, _ := self.blockDb.Get(append(blockNumPre, big.NewInt(int64(num)).Bytes()...)) @@ -482,45 +487,12 @@ func (self *ChainManager) GetUnclesInChain(block *types.Block, length int) (uncl return } -func (self *ChainManager) GetAncestors(block *types.Block, length int) (blocks []*types.Block) { - for i := 0; i < length; i++ { - block = self.GetBlock(block.ParentHash()) - if block == nil { - break - } - - blocks = append(blocks, block) - } - - 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 (self *ChainManager) CalcTotalDiff(block *types.Block) (*big.Int, error) { - parent := self.GetBlock(block.Header().ParentHash) - if parent == nil { - return nil, fmt.Errorf("Unable to calculate total diff without known parent %x", block.Header().ParentHash) - } - - parentTd := parent.Td - - uncleDiff := new(big.Int) - for _, uncle := range block.Uncles() { - uncleDiff = uncleDiff.Add(uncleDiff, uncle.Difficulty) - } - - td := new(big.Int) - td = td.Add(parentTd, uncleDiff) - td = td.Add(td, block.Header().Difficulty) - - return td, nil -} - func (bc *ChainManager) Stop() { close(bc.quit) atomic.StoreInt32(&bc.procInterrupt, 1) @@ -538,16 +510,94 @@ type queueEvent struct { } func (self *ChainManager) procFutureBlocks() { - var blocks []*types.Block - self.futureBlocks.Each(func(i int, block *types.Block) { - blocks = append(blocks, block) - }) + 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) } } +func (self *ChainManager) enqueueForWrite(block *types.Block) { + self.pendingBlocks.Add(block.Hash(), block) +} + +func (self *ChainManager) flushQueuedBlocks() { + db, batchWrite := self.blockDb.(*ethdb.LDBDatabase) + batch := new(leveldb.Batch) + for _, key := range self.pendingBlocks.Keys() { + b, _ := self.pendingBlocks.Get(key) + block := b.(*types.Block) + + enc, _ := rlp.EncodeToBytes((*types.StorageBlock)(block)) + key := append(blockHashPre, block.Hash().Bytes()...) + if batchWrite { + batch.Put(key, rle.Compress(enc)) + } else { + self.blockDb.Put(key, enc) + } + } + + if batchWrite { + db.LDB().Write(batch, nil) + } +} + +type writeStatus byte + +const ( + nonStatTy writeStatus = iota + canonStatTy + splitStatTy + sideStatTy +) + +func (self *ChainManager) WriteBlock(block *types.Block) (status writeStatus, err error) { + self.wg.Add(1) + defer self.wg.Done() + + cblock := self.currentBlock + // 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 block.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.merge(cblock, block) + if err != nil { + return nonStatTy, err + } + + status = splitStatTy + } + + self.mu.Lock() + self.setTotalDifficulty(block.Td) + self.insert(block) + self.mu.Unlock() + + self.setTransState(state.New(block.Root(), self.stateDb)) + self.txState.SetState(state.New(block.Root(), self.stateDb)) + + status = canonStatTy + } else { + status = sideStatTy + } + + // Write block to database. Eventually we'll have to improve on this and throw away blocks that are + // not in the canonical chain. + self.mu.Lock() + self.enqueueForWrite(block) + self.mu.Unlock() + // 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) { @@ -557,6 +607,8 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) { self.chainmu.Lock() defer self.chainmu.Unlock() + self.pendingBlocks, _ = lru.New(len(chain)) + // A queued approach to delivering events. This is generally // faster than direct delivery and requires much less mutex // acquiring. @@ -574,6 +626,7 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) { // Start the parallel nonce verifier. go verifyNonces(self.pow, chain, nonceQuit, nonceDone) defer close(nonceQuit) + defer self.flushQueuedBlocks() txcount := 0 for i, block := range chain { @@ -621,15 +674,13 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) { return i, fmt.Errorf("%v: BlockFutureErr, %v > %v", BlockFutureErr, block.Time(), max) } - block.SetQueued(true) - self.futureBlocks.Push(block) + self.futureBlocks.Add(block.Hash(), block) stats.queued++ continue } - if IsParentErr(err) && self.futureBlocks.Has(block.ParentHash()) { - block.SetQueued(true) - self.futureBlocks.Push(block) + if IsParentErr(err) && self.futureBlocks.Contains(block.ParentHash()) { + self.futureBlocks.Add(block.Hash(), block) stats.queued++ continue } @@ -641,59 +692,29 @@ func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) { txcount += len(block.Transactions()) - cblock := self.currentBlock - // 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 block.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.merge(cblock, block) - if err != nil { - return i, err - } - - queue[i] = ChainSplitEvent{block, logs} - queueEvent.splitCount++ - } - - self.mu.Lock() - self.setTotalDifficulty(block.Td) - self.insert(block) - self.mu.Unlock() - - jsonlogger.LogJson(&logger.EthChainNewHead{ - BlockHash: block.Hash().Hex(), - BlockNumber: block.Number(), - ChainHeadHash: cblock.Hash().Hex(), - BlockPrevHash: block.ParentHash().Hex(), - }) - - self.setTransState(state.New(block.Root(), self.stateDb)) - self.txState.SetState(state.New(block.Root(), self.stateDb)) - - queue[i] = ChainEvent{block, block.Hash(), logs} - queueEvent.canonicalCount++ - + // 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 %d UNCs) (%x...). Took %v\n", time.Now().UnixNano(), block.Number(), len(block.Transactions()), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart)) } - } else { + queue[i] = ChainEvent{block, block.Hash(), logs} + queueEvent.canonicalCount++ + 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++ } - // Write block to database. Eventually we'll have to improve on this and throw away blocks that are - // not in the canonical chain. - self.write(block) - // Delete from future blocks - self.futureBlocks.Delete(block.Hash()) - stats.processed++ - blockInsertTimer.UpdateSince(bstart) } if (stats.queued > 0 || stats.processed > 0 || stats.ignored > 0) && bool(glog.V(logger.Info)) { @@ -752,9 +773,9 @@ func (self *ChainManager) diff(oldBlock, newBlock *types.Block) (types.Blocks, e } } - if glog.V(logger.Info) { + if glog.V(logger.Debug) { commonHash := commonBlock.Hash() - glog.Infof("Fork detected @ %x. Reorganising chain from #%v %x to %x", commonHash[:4], numSplit, oldStart.Hash().Bytes()[:4], newStart.Hash().Bytes()[:4]) + glog.Infof("Chain split detected @ %x. Reorganising chain from #%v %x to %x", commonHash[:4], numSplit, oldStart.Hash().Bytes()[:4], newStart.Hash().Bytes()[:4]) } return newChain, nil diff --git a/core/chain_manager_test.go b/core/chain_manager_test.go index c56a3b3e1..8b3ea9e85 100644 --- a/core/chain_manager_test.go +++ b/core/chain_manager_test.go @@ -18,6 +18,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/rlp" + "github.com/hashicorp/golang-lru" ) func init() { @@ -62,12 +63,11 @@ func testFork(t *testing.T, bman *BlockProcessor, i, N int, f func(td1, td2 *big if 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(bman2, parent, N, db, ForkSeed) + chainB := makeChain(parent, N, db, forkSeed) _, err = bman2.bc.InsertChain(chainB) if err != nil { t.Fatal("Insert chain error for fork:", err) @@ -109,7 +109,8 @@ func testChain(chainB types.Blocks, bman *BlockProcessor) (*big.Int, error) { bman.bc.mu.Lock() { - bman.bc.write(block) + bman.bc.enqueueForWrite(block) + //bman.bc.write(block) } bman.bc.mu.Unlock() } @@ -117,7 +118,7 @@ func testChain(chainB types.Blocks, bman *BlockProcessor) (*big.Int, error) { } func loadChain(fn string, t *testing.T) (types.Blocks, error) { - fh, err := os.OpenFile(filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "_data", fn), os.O_RDONLY, os.ModePerm) + fh, err := os.OpenFile(filepath.Join("..", "_data", fn), os.O_RDONLY, os.ModePerm) if err != nil { return nil, err } @@ -256,7 +257,7 @@ func TestBrokenChain(t *testing.T) { } bman2.bc.SetProcessor(bman2) parent := bman2.bc.CurrentBlock() - chainB := makeChain(bman2, parent, 5, db2, ForkSeed) + chainB := makeChain(parent, 5, db2, forkSeed) chainB = chainB[1:] _, err = testChain(chainB, bman) if err == nil { @@ -265,7 +266,7 @@ func TestBrokenChain(t *testing.T) { } func TestChainInsertions(t *testing.T) { - t.Skip() // travil fails. + t.Skip("Skipped: outdated test files") db, _ := ethdb.NewMemDatabase() @@ -303,7 +304,7 @@ func TestChainInsertions(t *testing.T) { } func TestChainMultipleInsertions(t *testing.T) { - t.Skip() // travil fails. + t.Skip("Skipped: outdated test files") db, _ := ethdb.NewMemDatabase() @@ -346,8 +347,8 @@ func TestChainMultipleInsertions(t *testing.T) { } } -func TestGetAncestors(t *testing.T) { - t.Skip() // travil fails. +func TestGetBlocksFromHash(t *testing.T) { + t.Skip("Skipped: outdated test files") db, _ := ethdb.NewMemDatabase() chainMan := theChainManager(db, t) @@ -361,8 +362,8 @@ func TestGetAncestors(t *testing.T) { chainMan.write(block) } - ancestors := chainMan.GetAncestors(chain[len(chain)-1], 4) - fmt.Println(ancestors) + blocks := chainMan.GetBlocksFromHash(chain[len(chain)-1].Hash(), 4) + fmt.Println(blocks) } type bproc struct{} @@ -372,15 +373,17 @@ func (bproc) Process(*types.Block) (state.Logs, error) { return 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{Number: big.NewInt(int64(i + 1)), Difficulty: big.NewInt(int64(difficulty))} - block := types.NewBlockWithHeader(header) - copy(block.HeaderHash[:2], []byte{byte(i + 1), seed}) + header := &types.Header{ + Coinbase: common.Address{seed}, + Number: big.NewInt(int64(i + 1)), + Difficulty: big.NewInt(int64(difficulty)), + } if i == 0 { - block.ParentHeaderHash = genesis.Hash() + header.ParentHash = genesis.Hash() } else { - copy(block.ParentHeaderHash[:2], []byte{byte(i), seed}) + header.ParentHash = chain[i-1].Hash() } - + block := types.NewBlockWithHeader(header) chain = append(chain, block) } return chain @@ -389,8 +392,8 @@ func makeChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block func chm(genesis *types.Block, db common.Database) *ChainManager { var eventMux event.TypeMux bc := &ChainManager{blockDb: db, stateDb: db, genesisBlock: genesis, eventMux: &eventMux, pow: FakePow{}} - bc.cache = NewBlockCache(100) - bc.futureBlocks = NewBlockCache(100) + bc.cache, _ = lru.New(100) + bc.futureBlocks, _ = lru.New(100) bc.processor = bproc{} bc.ResetWithGenesisBlock(genesis) bc.txState = state.ManageState(bc.State()) @@ -399,7 +402,6 @@ func chm(genesis *types.Block, db common.Database) *ChainManager { } func TestReorgLongest(t *testing.T) { - t.Skip("skipped while cache is removed") db, _ := ethdb.NewMemDatabase() genesis := GenesisBlock(0, db) bc := chm(genesis, db) @@ -419,7 +421,6 @@ func TestReorgLongest(t *testing.T) { } func TestReorgShortest(t *testing.T) { - t.Skip("skipped while cache is removed") db, _ := ethdb.NewMemDatabase() genesis := GenesisBlock(0, db) bc := chm(genesis, db) @@ -444,7 +445,7 @@ func TestInsertNonceError(t *testing.T) { genesis := GenesisBlock(0, db) bc := chm(genesis, db) bc.processor = NewBlockProcessor(db, db, bc.pow, bc, bc.eventMux) - blocks := makeChain(bc.processor.(*BlockProcessor), bc.currentBlock, i, db, 0) + blocks := makeChain(bc.currentBlock, i, db, 0) fail := rand.Int() % len(blocks) failblock := blocks[fail] diff --git a/core/genesis.go b/core/genesis.go index dd894e0b0..df13466ec 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -3,6 +3,7 @@ package core import ( "encoding/json" "fmt" + "math/big" "os" "github.com/ethereum/go-ethereum/common" @@ -11,38 +12,18 @@ import ( "github.com/ethereum/go-ethereum/params" ) -/* - * This is the special genesis block. - */ - -var ZeroHash256 = make([]byte, 32) -var ZeroHash160 = make([]byte, 20) -var ZeroHash512 = make([]byte, 64) - +// GenesisBlock creates a genesis block with the given nonce. func GenesisBlock(nonce uint64, db common.Database) *types.Block { - genesis := types.NewBlock(common.Hash{}, common.Address{}, common.Hash{}, params.GenesisDifficulty, nonce, nil) - genesis.Header().Number = common.Big0 - genesis.Header().GasLimit = params.GenesisGasLimit - genesis.Header().GasUsed = common.Big0 - genesis.Header().Time = 0 - - genesis.Td = common.Big0 - - genesis.SetUncles([]*types.Header{}) - genesis.SetTransactions(types.Transactions{}) - genesis.SetReceipts(types.Receipts{}) - var accounts map[string]struct { Balance string Code string } err := json.Unmarshal(GenesisAccounts, &accounts) if err != nil { - fmt.Println("enable to decode genesis json data:", err) + fmt.Println("unable to decode genesis json data:", err) os.Exit(1) } - - statedb := state.New(genesis.Root(), db) + statedb := state.New(common.Hash{}, db) for addr, account := range accounts { codedAddr := common.Hex2Bytes(addr) accountState := statedb.CreateAccount(common.BytesToAddress(codedAddr)) @@ -51,10 +32,15 @@ func GenesisBlock(nonce uint64, db common.Database) *types.Block { statedb.UpdateStateObject(accountState) } statedb.Sync() - genesis.Header().Root = statedb.Root() - genesis.Td = params.GenesisDifficulty - return genesis + block := types.NewBlock(&types.Header{ + Difficulty: params.GenesisDifficulty, + GasLimit: params.GenesisGasLimit, + Nonce: types.EncodeNonce(nonce), + Root: statedb.Root(), + }, nil, nil, nil) + block.Td = params.GenesisDifficulty + return block } var GenesisAccounts = []byte(`{ @@ -71,3 +57,20 @@ var GenesisAccounts = []byte(`{ "e6716f9544a56c530d868e4bfbacb172315bdead": {"balance": "1606938044258990275541962092341162602522202993782792835301376"}, "1a26338f0d905e295fccb71fa9ea849ffa12aaf4": {"balance": "1606938044258990275541962092341162602522202993782792835301376"} }`) + +// GenesisBlockForTesting creates a block in which addr has the given wei balance. +// The state trie of the block is written to db. +func GenesisBlockForTesting(db common.Database, addr common.Address, balance *big.Int) *types.Block { + statedb := state.New(common.Hash{}, db) + obj := statedb.GetOrNewStateObject(addr) + obj.SetBalance(balance) + statedb.Update() + statedb.Sync() + block := types.NewBlock(&types.Header{ + Difficulty: params.GenesisDifficulty, + GasLimit: params.GenesisGasLimit, + Root: statedb.Root(), + }, nil, nil, nil) + block.Td = params.GenesisDifficulty + return block +} diff --git a/core/state_transition.go b/core/state_transition.go index 915dd466b..e2212dfef 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -13,10 +13,6 @@ import ( "github.com/ethereum/go-ethereum/params" ) -const tryJit = false - -var () - /* * The State transitioning model * @@ -69,20 +65,24 @@ func MessageCreatesContract(msg Message) bool { return msg.To() == nil } -func MessageGasValue(msg Message) *big.Int { - return new(big.Int).Mul(msg.Gas(), msg.GasPrice()) -} - -func IntrinsicGas(msg Message) *big.Int { +// IntrinsicGas computes the 'intrisic gas' for a message +// with the given data. +func IntrinsicGas(data []byte) *big.Int { igas := new(big.Int).Set(params.TxGas) - for _, byt := range msg.Data() { - if byt != 0 { - igas.Add(igas, params.TxDataNonZeroGas) - } else { - igas.Add(igas, params.TxDataZeroGas) + if len(data) > 0 { + var nz int64 + for _, byt := range data { + if byt != 0 { + nz++ + } } + m := big.NewInt(nz) + m.Mul(m, params.TxDataNonZeroGas) + igas.Add(igas, m) + m.SetInt64(int64(len(data)) - nz) + m.Mul(m, params.TxDataZeroGas) + igas.Add(igas, m) } - return igas } @@ -96,7 +96,7 @@ func NewStateTransition(env vm.Environment, msg Message, coinbase *state.StateOb env: env, msg: msg, gas: new(big.Int), - gasPrice: new(big.Int).Set(msg.GasPrice()), + gasPrice: msg.GasPrice(), initialGas: new(big.Int), value: msg.Value(), data: msg.Data(), @@ -140,26 +140,22 @@ func (self *StateTransition) AddGas(amount *big.Int) { } func (self *StateTransition) BuyGas() error { - var err error + mgas := self.msg.Gas() + mgval := new(big.Int).Mul(mgas, self.gasPrice) sender, err := self.From() if err != nil { return err } - if sender.Balance().Cmp(MessageGasValue(self.msg)) < 0 { - return fmt.Errorf("insufficient ETH for gas (%x). Req %v, has %v", sender.Address().Bytes()[:4], MessageGasValue(self.msg), sender.Balance()) + 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()) } - - coinbase := self.Coinbase() - err = coinbase.SubGas(self.msg.Gas(), self.msg.GasPrice()) - if err != nil { + if err = self.Coinbase().SubGas(mgas, self.gasPrice); err != nil { return err } - - self.AddGas(self.msg.Gas()) - self.initialGas.Set(self.msg.Gas()) - sender.SubBalance(MessageGasValue(self.msg)) - + self.AddGas(mgas) + self.initialGas.Set(mgas) + sender.SubBalance(mgval) return nil } @@ -195,14 +191,14 @@ func (self *StateTransition) transitionState() (ret []byte, usedGas *big.Int, er sender, _ := self.From() // err checked in preCheck // Pay intrinsic gas - if err = self.UseGas(IntrinsicGas(self.msg)); err != nil { + if err = self.UseGas(IntrinsicGas(self.data)); err != nil { return nil, nil, InvalidTxError(err) } vmenv := self.env var ref vm.ContextRef if MessageCreatesContract(msg) { - ret, err, ref = vmenv.Create(sender, self.msg.Data(), self.gas, self.gasPrice, self.value) + ret, err, ref = 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) @@ -216,7 +212,7 @@ func (self *StateTransition) transitionState() (ret []byte, usedGas *big.Int, er } 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.msg.Data(), self.gas, self.gasPrice, self.value) + ret, err = vmenv.Call(sender, self.To().Address(), self.data, self.gas, self.gasPrice, self.value) } if err != nil && IsValueTransferErr(err) { @@ -237,15 +233,15 @@ func (self *StateTransition) refundGas() { coinbase := self.Coinbase() sender, _ := self.From() // err already checked // Return remaining gas - remaining := new(big.Int).Mul(self.gas, self.msg.GasPrice()) + remaining := new(big.Int).Mul(self.gas, self.gasPrice) sender.AddBalance(remaining) - uhalf := new(big.Int).Div(self.gasUsed(), common.Big2) + uhalf := remaining.Div(self.gasUsed(), common.Big2) refund := common.BigMin(uhalf, self.state.Refunds()) self.gas.Add(self.gas, refund) - self.state.AddBalance(sender.Address(), refund.Mul(refund, self.msg.GasPrice())) + self.state.AddBalance(sender.Address(), refund.Mul(refund, self.gasPrice)) - coinbase.AddGas(self.gas, self.msg.GasPrice()) + coinbase.AddGas(self.gas, self.gasPrice) } func (self *StateTransition) gasUsed() *big.Int { diff --git a/core/transaction_pool.go b/core/transaction_pool.go index 34a1733d7..bf28647c3 100644 --- a/core/transaction_pool.go +++ b/core/transaction_pool.go @@ -162,27 +162,25 @@ func (pool *TxPool) validateTx(tx *types.Transaction) error { // Check the transaction doesn't exceed the current // block limit gas. - if pool.gasLimit().Cmp(tx.GasLimit) < 0 { + if pool.gasLimit().Cmp(tx.Gas()) < 0 { return ErrGasLimit } // Transactions can't be negative. This may never happen // using RLP decoded transactions but may occur if you create // a transaction using the RPC for example. - if tx.Amount.Cmp(common.Big0) < 0 { + if tx.Value().Cmp(common.Big0) < 0 { return ErrNegativeValue } // Transactor should have enough funds to cover the costs // cost == V + GP * GL - total := new(big.Int).Mul(tx.Price, tx.GasLimit) - total.Add(total, tx.Value()) - if pool.currentState().GetBalance(from).Cmp(total) < 0 { + if pool.currentState().GetBalance(from).Cmp(tx.Cost()) < 0 { return ErrInsufficientFunds } // Should supply enough intrinsic gas - if tx.GasLimit.Cmp(IntrinsicGas(tx)) < 0 { + if tx.Gas().Cmp(IntrinsicGas(tx.Data())) < 0 { return ErrIntrinsicGas } @@ -238,7 +236,7 @@ func (pool *TxPool) addTx(hash common.Hash, addr common.Address, tx *types.Trans // Increment the nonce on the pending state. This can only happen if // the nonce is +1 to the previous one. - pool.pendingState.SetNonce(addr, tx.AccountNonce+1) + pool.pendingState.SetNonce(addr, tx.Nonce()+1) // Notify the subscribers. This event is posted in a goroutine // because it's possible that somewhere during the post "Remove transaction" // gets called which will then wait for the global tx pool lock and deadlock. @@ -341,7 +339,7 @@ func (pool *TxPool) checkQueue() { trueNonce := pool.currentState().GetNonce(address) addq := addq[:0] for hash, tx := range txs { - if tx.AccountNonce < trueNonce { + if tx.Nonce() < trueNonce { // Drop queued transactions whose nonce is lower than // the account nonce because they have been processed. delete(txs, hash) @@ -362,8 +360,7 @@ func (pool *TxPool) checkQueue() { delete(pool.queue[address], e.hash) continue } - - if e.AccountNonce > guessedNonce { + if e.Nonce() > guessedNonce { break } delete(txs, e.hash) @@ -418,4 +415,4 @@ type txQueueEntry struct { func (q txQueue) Len() int { return len(q) } func (q txQueue) Swap(i, j int) { q[i], q[j] = q[j], q[i] } -func (q txQueue) Less(i, j int) bool { return q[i].AccountNonce < q[j].AccountNonce } +func (q txQueue) Less(i, j int) bool { return q[i].Nonce() < q[j].Nonce() } diff --git a/core/transaction_pool_test.go b/core/transaction_pool_test.go index b763c196d..ff8b9c730 100644 --- a/core/transaction_pool_test.go +++ b/core/transaction_pool_test.go @@ -13,8 +13,9 @@ import ( "github.com/ethereum/go-ethereum/event" ) -func transaction() *types.Transaction { - return types.NewTransactionMessage(common.Address{}, big.NewInt(100), big.NewInt(100), big.NewInt(100), nil) +func transaction(nonce uint64, gaslimit *big.Int, key *ecdsa.PrivateKey) *types.Transaction { + tx, _ := types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, big.NewInt(1), nil).SignECDSA(key) + return tx } func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { @@ -29,43 +30,34 @@ func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { func TestInvalidTransactions(t *testing.T) { pool, key := setupTxPool() - tx := transaction() - tx.SignECDSA(key) - err := pool.Add(tx) - if err != ErrNonExistentAccount { + tx := transaction(0, big.NewInt(100), key) + if err := pool.Add(tx); err != ErrNonExistentAccount { t.Error("expected", ErrNonExistentAccount) } from, _ := tx.From() pool.currentState().AddBalance(from, big.NewInt(1)) - err = pool.Add(tx) - if err != ErrInsufficientFunds { + 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) - err = pool.Add(tx) - if err != ErrIntrinsicGas { + 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)) - tx.GasLimit = big.NewInt(100000) - tx.Price = big.NewInt(1) - tx.SignECDSA(key) - - err = pool.Add(tx) - if err != ErrNonce { + tx = transaction(0, big.NewInt(100000), key) + if err := pool.Add(tx); err != ErrNonce { t.Error("expected", ErrNonce) } } func TestTransactionQueue(t *testing.T) { pool, key := setupTxPool() - tx := transaction() - tx.SignECDSA(key) + tx := transaction(0, big.NewInt(100), key) from, _ := tx.From() pool.currentState().AddBalance(from, big.NewInt(1)) pool.queueTx(tx.Hash(), tx) @@ -75,9 +67,7 @@ func TestTransactionQueue(t *testing.T) { t.Error("expected valid txs to be 1 is", len(pool.pending)) } - tx = transaction() - tx.SetNonce(1) - tx.SignECDSA(key) + tx = transaction(1, big.NewInt(100), key) from, _ = tx.From() pool.currentState().SetNonce(from, 2) pool.queueTx(tx.Hash(), tx) @@ -91,12 +81,9 @@ func TestTransactionQueue(t *testing.T) { } pool, key = setupTxPool() - tx1, tx2, tx3 := transaction(), transaction(), transaction() - tx2.SetNonce(10) - tx3.SetNonce(11) - tx1.SignECDSA(key) - tx2.SignECDSA(key) - tx3.SignECDSA(key) + tx1 := transaction(0, big.NewInt(100), key) + tx2 := transaction(10, big.NewInt(100), key) + tx3 := transaction(11, big.NewInt(100), key) pool.queueTx(tx1.Hash(), tx1) pool.queueTx(tx2.Hash(), tx2) pool.queueTx(tx3.Hash(), tx3) @@ -114,8 +101,7 @@ func TestTransactionQueue(t *testing.T) { func TestRemoveTx(t *testing.T) { pool, key := setupTxPool() - tx := transaction() - tx.SignECDSA(key) + tx := transaction(0, big.NewInt(100), key) from, _ := tx.From() pool.currentState().AddBalance(from, big.NewInt(1)) pool.queueTx(tx.Hash(), tx) @@ -142,13 +128,10 @@ func TestRemoveTx(t *testing.T) { func TestNegativeValue(t *testing.T) { pool, key := setupTxPool() - tx := transaction() - tx.Value().Set(big.NewInt(-1)) - tx.SignECDSA(key) + 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)) - err := pool.Add(tx) - if err != ErrNegativeValue { + if err := pool.Add(tx); err != ErrNegativeValue { t.Error("expected", ErrNegativeValue, "got", err) } } @@ -165,20 +148,15 @@ func TestTransactionChainFork(t *testing.T) { } resetState() - tx := transaction() - tx.GasLimit = big.NewInt(100000) - tx.SignECDSA(key) - - err := pool.add(tx) - if err != nil { + tx := transaction(0, big.NewInt(100000), key) + if err := pool.add(tx); err != nil { t.Error("didn't expect error", err) } pool.RemoveTransactions([]*types.Transaction{tx}) // reset the pool's internal state resetState() - err = pool.add(tx) - if err != nil { + if err := pool.add(tx); err != nil { t.Error("didn't expect error", err) } } @@ -195,24 +173,14 @@ func TestTransactionDoubleNonce(t *testing.T) { } resetState() - tx := transaction() - tx.GasLimit = big.NewInt(100000) - tx.SignECDSA(key) - - err := pool.add(tx) - if err != nil { + tx := transaction(0, big.NewInt(100000), key) + tx2 := transaction(0, big.NewInt(1000000), key) + if err := pool.add(tx); err != nil { t.Error("didn't expect error", err) } - - tx2 := transaction() - tx2.GasLimit = big.NewInt(1000000) - tx2.SignECDSA(key) - - err = pool.add(tx2) - if err != nil { + if err := pool.add(tx2); err != nil { t.Error("didn't expect error", err) } - if len(pool.pending) != 2 { t.Error("expected 2 pending txs. Got", len(pool.pending)) } @@ -222,20 +190,13 @@ func TestMissingNonce(t *testing.T) { pool, key := setupTxPool() addr := crypto.PubkeyToAddress(key.PublicKey) pool.currentState().AddBalance(addr, big.NewInt(100000000000000)) - tx := transaction() - tx.AccountNonce = 1 - tx.GasLimit = big.NewInt(100000) - tx.SignECDSA(key) - - err := pool.add(tx) - if err != nil { + tx := transaction(1, big.NewInt(100000), key) + if err := pool.add(tx); err != nil { t.Error("didn't expect error", err) } - if len(pool.pending) != 0 { t.Error("expected 0 pending transactions, got", len(pool.pending)) } - if len(pool.queue[addr]) != 1 { t.Error("expected 1 queued transaction, got", len(pool.queue[addr])) } diff --git a/core/types/block.go b/core/types/block.go index d7963981e..b7eb700ca 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -8,6 +8,7 @@ import ( "io" "math/big" "sort" + "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" @@ -15,71 +16,59 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -type Header struct { - // Hash to the previous block - ParentHash common.Hash - // Uncles of this block - UncleHash common.Hash - // The coin base address - Coinbase common.Address - // Block Trie state - Root common.Hash - // Tx sha - TxHash common.Hash - // Receipt sha - ReceiptHash common.Hash - // Bloom - Bloom Bloom - // Difficulty for the current block - Difficulty *big.Int - // The block number - Number *big.Int - // Gas limit - GasLimit *big.Int - // Gas used - GasUsed *big.Int - // Creation time - Time uint64 - // Extra data - Extra []byte - // Mix digest for quick checking to prevent DOS - MixDigest common.Hash - // Nonce - Nonce [8]byte -} +// A BlockNonce is a 64-bit hash which proves (combined with the +// mix-hash) that a suffcient amount of computation has been carried +// out on a block. +type BlockNonce [8]byte -func (self *Header) Hash() common.Hash { - return rlpHash(self.rlpData(true)) +func EncodeNonce(i uint64) BlockNonce { + var n BlockNonce + binary.BigEndian.PutUint64(n[:], i) + return n } -func (self *Header) HashNoNonce() common.Hash { - return rlpHash(self.rlpData(false)) +func (n BlockNonce) Uint64() uint64 { + return binary.BigEndian.Uint64(n[:]) } -func (self *Header) rlpData(withNonce bool) []interface{} { - fields := []interface{}{ - self.ParentHash, - self.UncleHash, - self.Coinbase, - self.Root, - self.TxHash, - self.ReceiptHash, - self.Bloom, - self.Difficulty, - self.Number, - self.GasLimit, - self.GasUsed, - self.Time, - self.Extra, - } - if withNonce { - fields = append(fields, self.MixDigest, self.Nonce) - } - return fields -} - -func (self *Header) RlpData() interface{} { - return self.rlpData(true) +type Header struct { + ParentHash common.Hash // Hash to the previous block + UncleHash common.Hash // Uncles of this block + Coinbase common.Address // The coin base address + Root common.Hash // Block Trie state + TxHash common.Hash // Tx sha + ReceiptHash common.Hash // Receipt sha + Bloom Bloom // Bloom + Difficulty *big.Int // Difficulty for the current block + Number *big.Int // The block number + GasLimit *big.Int // Gas limit + GasUsed *big.Int // Gas used + Time uint64 // Creation time + Extra []byte // Extra data + MixDigest common.Hash // for quick difficulty verification + Nonce BlockNonce +} + +func (h *Header) Hash() common.Hash { + return rlpHash(h) +} + +func (h *Header) HashNoNonce() common.Hash { + return rlpHash([]interface{}{ + h.ParentHash, + h.UncleHash, + h.Coinbase, + h.Root, + h.TxHash, + h.ReceiptHash, + h.Bloom, + h.Difficulty, + h.Number, + h.GasLimit, + h.GasUsed, + h.Time, + h.Extra, + }) } func (h *Header) UnmarshalJSON(data []byte) error { @@ -112,20 +101,21 @@ func rlpHash(x interface{}) (h common.Hash) { } type Block struct { - // Preset Hash for mock (Tests) - HeaderHash common.Hash - ParentHeaderHash common.Hash - // ^^^^ ignore ^^^^ - header *Header uncles []*Header transactions Transactions - Td *big.Int - queued bool // flag for blockpool to skip TD check + receipts Receipts - ReceivedAt time.Time + // caches + hash atomic.Value + size atomic.Value + + // Td is used by package core to store the total difficulty + // of the chain up to and including the block. + Td *big.Int - receipts Receipts + // ReceivedAt is used by package eth to track block propagation time. + ReceivedAt time.Time } // StorageBlock defines the RLP encoding of a Block stored in the @@ -148,43 +138,90 @@ type storageblock struct { TD *big.Int } -func NewBlock(parentHash common.Hash, coinbase common.Address, root common.Hash, difficulty *big.Int, nonce uint64, extra []byte) *Block { - header := &Header{ - Root: root, - ParentHash: parentHash, - Coinbase: coinbase, - Difficulty: difficulty, - Time: uint64(time.Now().Unix()), - Extra: extra, - GasUsed: new(big.Int), - GasLimit: new(big.Int), - Number: new(big.Int), +var ( + emptyRootHash = DeriveSha(Transactions{}) + emptyUncleHash = CalcUncleHash(nil) +) + +// NewBlock creates a new block. The input data is copied, +// changes to header and to the field values will not affect the +// block. +// +// The values of TxHash, UncleHash, ReceiptHash and Bloom in header +// 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)} + + // TODO: panic if len(txs) != len(receipts) + if len(txs) == 0 { + b.header.TxHash = emptyRootHash + } else { + b.header.TxHash = DeriveSha(Transactions(txs)) + b.transactions = make(Transactions, len(txs)) + copy(b.transactions, txs) } - header.SetNonce(nonce) - block := &Block{header: header} - block.Td = new(big.Int) - return block -} + if len(receipts) == 0 { + 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 + } else { + b.header.UncleHash = CalcUncleHash(uncles) + b.uncles = make([]*Header, len(uncles)) + for i := range uncles { + b.uncles[i] = copyHeader(uncles[i]) + } + } -func (self *Header) SetNonce(nonce uint64) { - binary.BigEndian.PutUint64(self.Nonce[:], nonce) + return b } +// NewBlockWithHeader creates a block with the given header data. The +// 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: header} + return &Block{header: copyHeader(header)} } -func (self *Block) ValidateFields() error { - if self.header == nil { +func copyHeader(h *Header) *Header { + cpy := *h + if cpy.Difficulty = new(big.Int); h.Difficulty != nil { + cpy.Difficulty.Set(h.Difficulty) + } + if cpy.Number = new(big.Int); h.Number != nil { + cpy.Number.Set(h.Number) + } + if cpy.GasLimit = new(big.Int); h.GasLimit != nil { + cpy.GasLimit.Set(h.GasLimit) + } + if cpy.GasUsed = new(big.Int); h.GasUsed != nil { + cpy.GasUsed.Set(h.GasUsed) + } + if len(h.Extra) > 0 { + cpy.Extra = make([]byte, len(h.Extra)) + copy(cpy.Extra, h.Extra) + } + return &cpy +} + +func (b *Block) ValidateFields() error { + if b.header == nil { return fmt.Errorf("header is nil") } - for i, transaction := range self.transactions { + for i, transaction := range b.transactions { if transaction == nil { return fmt.Errorf("transaction %d is nil", i) } } - for i, uncle := range self.uncles { + for i, uncle := range b.uncles { if uncle == nil { return fmt.Errorf("uncle %d is nil", i) } @@ -192,64 +229,50 @@ func (self *Block) ValidateFields() error { return nil } -func (self *Block) DecodeRLP(s *rlp.Stream) error { +func (b *Block) DecodeRLP(s *rlp.Stream) error { var eb extblock + _, size, _ := s.Kind() if err := s.Decode(&eb); err != nil { return err } - self.header, self.uncles, self.transactions = eb.Header, eb.Uncles, eb.Txs + b.header, b.uncles, b.transactions = eb.Header, eb.Uncles, eb.Txs + b.size.Store(common.StorageSize(rlp.ListSize(size))) return nil } -func (self Block) EncodeRLP(w io.Writer) error { +func (b Block) EncodeRLP(w io.Writer) error { return rlp.Encode(w, extblock{ - Header: self.header, - Txs: self.transactions, - Uncles: self.uncles, + Header: b.header, + Txs: b.transactions, + Uncles: b.uncles, }) } -func (self *StorageBlock) DecodeRLP(s *rlp.Stream) error { +func (b *StorageBlock) DecodeRLP(s *rlp.Stream) error { var sb storageblock if err := s.Decode(&sb); err != nil { return err } - self.header, self.uncles, self.transactions, self.Td = sb.Header, sb.Uncles, sb.Txs, sb.TD + b.header, b.uncles, b.transactions, b.Td = sb.Header, sb.Uncles, sb.Txs, sb.TD return nil } -func (self StorageBlock) EncodeRLP(w io.Writer) error { +func (b StorageBlock) EncodeRLP(w io.Writer) error { return rlp.Encode(w, storageblock{ - Header: self.header, - Txs: self.transactions, - Uncles: self.uncles, - TD: self.Td, + Header: b.header, + Txs: b.transactions, + Uncles: b.uncles, + TD: b.Td, }) } -func (self *Block) Header() *Header { - return self.header -} - -func (self *Block) Uncles() []*Header { - return self.uncles -} - -func (self *Block) CalculateUnclesHash() common.Hash { - return rlpHash(self.uncles) -} +// 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 (self *Block) SetUncles(uncleHeaders []*Header) { - self.uncles = uncleHeaders - self.header.UncleHash = rlpHash(uncleHeaders) -} - -func (self *Block) Transactions() Transactions { - return self.transactions -} - -func (self *Block) Transaction(hash common.Hash) *Transaction { - for _, transaction := range self.transactions { +func (b *Block) Transaction(hash common.Hash) *Transaction { + for _, transaction := range b.transactions { if transaction.Hash() == hash { return transaction } @@ -257,74 +280,37 @@ func (self *Block) Transaction(hash common.Hash) *Transaction { return nil } -func (self *Block) SetTransactions(transactions Transactions) { - self.transactions = transactions - self.header.TxHash = DeriveSha(transactions) -} -func (self *Block) AddTransaction(transaction *Transaction) { - self.transactions = append(self.transactions, transaction) - self.SetTransactions(self.transactions) -} +func (b *Block) Number() *big.Int { return new(big.Int).Set(b.header.Number) } +func (b *Block) GasLimit() *big.Int { return new(big.Int).Set(b.header.GasLimit) } +func (b *Block) GasUsed() *big.Int { return new(big.Int).Set(b.header.GasUsed) } +func (b *Block) Difficulty() *big.Int { return new(big.Int).Set(b.header.Difficulty) } -func (self *Block) Receipts() Receipts { - return self.receipts -} +func (b *Block) NumberU64() uint64 { return b.header.Number.Uint64() } +func (b *Block) MixDigest() common.Hash { return b.header.MixDigest } +func (b *Block) Nonce() uint64 { return binary.BigEndian.Uint64(b.header.Nonce[:]) } +func (b *Block) Bloom() Bloom { return b.header.Bloom } +func (b *Block) Coinbase() common.Address { return b.header.Coinbase } +func (b *Block) Time() int64 { return int64(b.header.Time) } +func (b *Block) Root() common.Hash { return b.header.Root } +func (b *Block) ParentHash() common.Hash { return b.header.ParentHash } +func (b *Block) TxHash() common.Hash { return b.header.TxHash } +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 (self *Block) SetReceipts(receipts Receipts) { - self.receipts = receipts - self.header.ReceiptHash = DeriveSha(receipts) - self.header.Bloom = CreateBloom(receipts) -} -func (self *Block) AddReceipt(receipt *Receipt) { - self.receipts = append(self.receipts, receipt) - self.SetReceipts(self.receipts) -} +func (b *Block) Header() *Header { return copyHeader(b.header) } -func (self *Block) RlpData() interface{} { - return []interface{}{self.header, self.transactions, self.uncles} +func (b *Block) HashNoNonce() common.Hash { + return b.header.HashNoNonce() } -func (self *Block) RlpDataForStorage() interface{} { - return []interface{}{self.header, self.transactions, self.uncles, self.Td /* TODO receipts */} -} - -// Header accessors (add as you need them) -func (self *Block) Number() *big.Int { return self.header.Number } -func (self *Block) NumberU64() uint64 { return self.header.Number.Uint64() } -func (self *Block) MixDigest() common.Hash { return self.header.MixDigest } -func (self *Block) Nonce() uint64 { - return binary.BigEndian.Uint64(self.header.Nonce[:]) -} -func (self *Block) SetNonce(nonce uint64) { - self.header.SetNonce(nonce) -} - -func (self *Block) Queued() bool { return self.queued } -func (self *Block) SetQueued(q bool) { self.queued = q } - -func (self *Block) Bloom() Bloom { return self.header.Bloom } -func (self *Block) Coinbase() common.Address { return self.header.Coinbase } -func (self *Block) Time() int64 { return int64(self.header.Time) } -func (self *Block) GasLimit() *big.Int { return self.header.GasLimit } -func (self *Block) GasUsed() *big.Int { return self.header.GasUsed } -func (self *Block) Root() common.Hash { return self.header.Root } -func (self *Block) SetRoot(root common.Hash) { self.header.Root = root } -func (self *Block) GetTransaction(i int) *Transaction { - if len(self.transactions) > i { - return self.transactions[i] - } - return nil -} -func (self *Block) GetUncle(i int) *Header { - if len(self.uncles) > i { - return self.uncles[i] +func (b *Block) Size() common.StorageSize { + if size := b.size.Load(); size != nil { + return size.(common.StorageSize) } - return nil -} - -func (self *Block) Size() common.StorageSize { c := writeCounter(0) - rlp.Encode(&c, self) + rlp.Encode(&c, b) + b.size.Store(common.StorageSize(c)) return common.StorageSize(c) } @@ -335,48 +321,37 @@ func (c *writeCounter) Write(b []byte) (int, error) { return len(b), nil } -// Implement pow.Block -func (self *Block) Difficulty() *big.Int { return self.header.Difficulty } -func (self *Block) HashNoNonce() common.Hash { return self.header.HashNoNonce() } - -func (self *Block) Hash() common.Hash { - if (self.HeaderHash != common.Hash{}) { - return self.HeaderHash - } else { - return self.header.Hash() - } +func CalcUncleHash(uncles []*Header) common.Hash { + return rlpHash(uncles) } -func (self *Block) ParentHash() common.Hash { - if (self.ParentHeaderHash != common.Hash{}) { - return self.ParentHeaderHash - } else { - return self.header.ParentHash +// WithMiningResult returns a new block with the data from b +// where nonce and mix digest are set to the provided values. +func (b *Block) WithMiningResult(nonce uint64, mixDigest common.Hash) *Block { + cpy := *b.header + binary.BigEndian.PutUint64(cpy.Nonce[:], nonce) + cpy.MixDigest = mixDigest + return &Block{ + header: &cpy, + transactions: b.transactions, + receipts: b.receipts, + uncles: b.uncles, + Td: b.Td, } } -func (self *Block) Copy() *Block { - block := NewBlock(self.header.ParentHash, self.Coinbase(), self.Root(), new(big.Int), self.Nonce(), self.header.Extra) - block.header.Bloom = self.header.Bloom - block.header.TxHash = self.header.TxHash - block.transactions = self.transactions - block.header.UncleHash = self.header.UncleHash - block.uncles = self.uncles - block.header.GasLimit.Set(self.header.GasLimit) - block.header.GasUsed.Set(self.header.GasUsed) - block.header.ReceiptHash = self.header.ReceiptHash - block.header.Difficulty.Set(self.header.Difficulty) - block.header.Number.Set(self.header.Number) - block.header.Time = self.header.Time - block.header.MixDigest = self.header.MixDigest - if self.Td != nil { - block.Td.Set(self.Td) - } +// Implement pow.Block - return block +func (b *Block) Hash() common.Hash { + if hash := b.hash.Load(); hash != nil { + return hash.(common.Hash) + } + v := rlpHash(b.header) + b.hash.Store(v) + return v } -func (self *Block) String() string { +func (b *Block) String() string { str := fmt.Sprintf(`Block(#%v): Size: %v TD: %v { MinerHash: %x %v @@ -385,20 +360,11 @@ Transactions: Uncles: %v } -`, self.Number(), self.Size(), self.Td, self.header.HashNoNonce(), self.header, self.transactions, self.uncles) - - if (self.HeaderHash != common.Hash{}) { - str += fmt.Sprintf("\nFake hash = %x", self.HeaderHash) - } - - if (self.ParentHeaderHash != common.Hash{}) { - str += fmt.Sprintf("\nFake parent hash = %x", self.ParentHeaderHash) - } - +`, b.Number(), b.Size(), b.Td, b.header.HashNoNonce(), b.header, b.transactions, b.uncles) return str } -func (self *Header) String() string { +func (h *Header) String() string { return fmt.Sprintf(`Header(%x): [ ParentHash: %x @@ -414,9 +380,9 @@ func (self *Header) String() string { GasUsed: %v Time: %v Extra: %s - MixDigest: %x + MixDigest: %x Nonce: %x -]`, self.Hash(), self.ParentHash, self.UncleHash, self.Coinbase, self.Root, self.TxHash, self.ReceiptHash, self.Bloom, self.Difficulty, self.Number, self.GasLimit, self.GasUsed, self.Time, self.Extra, self.MixDigest, self.Nonce) +]`, h.Hash(), h.ParentHash, h.UncleHash, h.Coinbase, h.Root, h.TxHash, h.ReceiptHash, h.Bloom, h.Difficulty, h.Number, h.GasLimit, h.GasUsed, h.Time, h.Extra, h.MixDigest, h.Nonce) } type Blocks []*Block @@ -442,4 +408,4 @@ func (self blockSorter) Swap(i, j int) { } func (self blockSorter) Less(i, j int) bool { return self.by(self.blocks[i], self.blocks[j]) } -func Number(b1, b2 *Block) bool { return b1.Header().Number.Cmp(b2.Header().Number) < 0 } +func Number(b1, b2 *Block) bool { return b1.header.Number.Cmp(b2.header.Number) < 0 } diff --git a/core/types/block_test.go b/core/types/block_test.go index b52ddffdc..03e6881be 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -13,7 +13,6 @@ import ( // from bcValidBlockTest.json, "SimpleTx" func TestBlockEncoding(t *testing.T) { blockEnc := common.FromHex("f90260f901f9a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f861f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1c0") - var block Block if err := rlp.DecodeBytes(blockEnc, &block); err != nil { t.Fatal("decode error: ", err) @@ -35,20 +34,10 @@ func TestBlockEncoding(t *testing.T) { check("Time", block.Time(), int64(1426516743)) check("Size", block.Size(), common.StorageSize(len(blockEnc))) - to := common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87") - check("Transactions", block.Transactions(), Transactions{ - { - Payload: []byte{}, - Amount: big.NewInt(10), - Price: big.NewInt(10), - GasLimit: big.NewInt(50000), - AccountNonce: 0, - V: 27, - R: common.String2Big("0x9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f"), - S: common.String2Big("0x8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1"), - Recipient: &to, - }, - }) + tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), big.NewInt(50000), big.NewInt(10), nil) + tx1, _ = tx1.WithSignature(common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")) + check("len(Transactions)", len(block.Transactions()), 1) + check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash()) ourBlockEnc, err := rlp.EncodeToBytes(&block) if err != nil { diff --git a/core/types/transaction.go b/core/types/transaction.go index a03a6b847..95deac36e 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -4,7 +4,9 @@ import ( "crypto/ecdsa" "errors" "fmt" + "io" "math/big" + "sync/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -18,38 +20,63 @@ func IsContractAddr(addr []byte) bool { } type Transaction struct { - AccountNonce uint64 - Price *big.Int - GasLimit *big.Int - Recipient *common.Address `rlp:"nil"` // nil means contract creation - Amount *big.Int - Payload []byte - V byte - R, S *big.Int + data txdata + // caches + hash atomic.Value + size atomic.Value + from atomic.Value } -func NewContractCreationTx(amount, gasLimit, gasPrice *big.Int, data []byte) *Transaction { - return &Transaction{ - Recipient: nil, - Amount: amount, - GasLimit: gasLimit, - Price: gasPrice, - Payload: data, - R: new(big.Int), - S: new(big.Int), +type txdata struct { + AccountNonce uint64 + Price, GasLimit *big.Int + Recipient *common.Address `rlp:"nil"` // nil means contract creation + Amount *big.Int + Payload []byte + V byte // signature + R, S *big.Int // signature +} + +func NewContractCreation(nonce uint64, amount, gasLimit, gasPrice *big.Int, data []byte) *Transaction { + if len(data) > 0 { + data = common.CopyBytes(data) } + return &Transaction{data: txdata{ + AccountNonce: nonce, + Recipient: nil, + Amount: new(big.Int).Set(amount), + GasLimit: new(big.Int).Set(gasLimit), + Price: new(big.Int).Set(gasPrice), + Payload: data, + R: new(big.Int), + S: new(big.Int), + }} } -func NewTransactionMessage(to common.Address, amount, gasAmount, gasPrice *big.Int, data []byte) *Transaction { - return &Transaction{ - Recipient: &to, - Amount: amount, - GasLimit: gasAmount, - Price: gasPrice, - Payload: data, - R: new(big.Int), - S: new(big.Int), +func NewTransaction(nonce uint64, to common.Address, amount, gasLimit, gasPrice *big.Int, data []byte) *Transaction { + if len(data) > 0 { + data = common.CopyBytes(data) + } + d := txdata{ + AccountNonce: nonce, + Recipient: &to, + Payload: data, + Amount: new(big.Int), + GasLimit: new(big.Int), + Price: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + if amount != nil { + d.Amount.Set(amount) + } + if gasLimit != nil { + d.GasLimit.Set(gasLimit) } + if gasPrice != nil { + d.Price.Set(gasPrice) + } + return &Transaction{data: d} } func NewTransactionFromBytes(data []byte) *Transaction { @@ -61,112 +88,128 @@ func NewTransactionFromBytes(data []byte) *Transaction { return tx } -func (tx *Transaction) Hash() common.Hash { - return rlpHash([]interface{}{ - tx.AccountNonce, tx.Price, tx.GasLimit, tx.Recipient, tx.Amount, tx.Payload, - }) -} - -// Size returns the encoded RLP size of tx. -func (self *Transaction) Size() common.StorageSize { - c := writeCounter(0) - rlp.Encode(&c, self) - return common.StorageSize(c) +func (tx *Transaction) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, &tx.data) } -func (self *Transaction) Data() []byte { - return self.Payload -} - -func (self *Transaction) Gas() *big.Int { - return self.GasLimit +func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { + _, size, _ := s.Kind() + err := s.Decode(&tx.data) + if err == nil { + tx.size.Store(common.StorageSize(rlp.ListSize(size))) + } + return err } -func (self *Transaction) GasPrice() *big.Int { - return self.Price -} +func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) } +func (tx *Transaction) Gas() *big.Int { return new(big.Int).Set(tx.data.GasLimit) } +func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) } +func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) } +func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce } -func (self *Transaction) Value() *big.Int { - return self.Amount +func (tx *Transaction) To() *common.Address { + if tx.data.Recipient == nil { + return nil + } else { + to := *tx.data.Recipient + return &to + } } -func (self *Transaction) Nonce() uint64 { - return self.AccountNonce +func (tx *Transaction) Hash() common.Hash { + if hash := tx.hash.Load(); hash != nil { + return hash.(common.Hash) + } + v := rlpHash([]interface{}{ + tx.data.AccountNonce, + tx.data.Price, + tx.data.GasLimit, + tx.data.Recipient, + tx.data.Amount, + tx.data.Payload, + }) + tx.hash.Store(v) + return v } -func (self *Transaction) SetNonce(AccountNonce uint64) { - self.AccountNonce = AccountNonce +func (tx *Transaction) Size() common.StorageSize { + if size := tx.size.Load(); size != nil { + return size.(common.StorageSize) + } + c := writeCounter(0) + rlp.Encode(&c, &tx.data) + tx.size.Store(common.StorageSize(c)) + return common.StorageSize(c) } -func (self *Transaction) From() (common.Address, error) { - pubkey, err := self.PublicKey() +func (tx *Transaction) From() (common.Address, error) { + if from := tx.from.Load(); from != nil { + return from.(common.Address), nil + } + pubkey, err := tx.publicKey() if err != nil { return common.Address{}, err } - var addr common.Address copy(addr[:], crypto.Sha3(pubkey[1:])[12:]) + tx.from.Store(addr) return addr, nil } -// To returns the recipient of the transaction. -// If transaction is a contract creation (with no recipient address) -// To returns nil. -func (tx *Transaction) To() *common.Address { - return tx.Recipient +// Cost returns amount + gasprice * gaslimit. +func (tx *Transaction) Cost() *big.Int { + total := new(big.Int).Mul(tx.data.Price, tx.data.GasLimit) + total.Add(total, tx.data.Amount) + return total } -func (tx *Transaction) GetSignatureValues() (v byte, r []byte, s []byte) { - v = byte(tx.V) - r = common.LeftPadBytes(tx.R.Bytes(), 32) - s = common.LeftPadBytes(tx.S.Bytes(), 32) - return +func (tx *Transaction) SignatureValues() (v byte, r *big.Int, s *big.Int) { + return tx.data.V, new(big.Int).Set(tx.data.R), new(big.Int).Set(tx.data.S) } -func (tx *Transaction) PublicKey() ([]byte, error) { - if !crypto.ValidateSignatureValues(tx.V, tx.R, tx.S) { +func (tx *Transaction) publicKey() ([]byte, error) { + if !crypto.ValidateSignatureValues(tx.data.V, tx.data.R, tx.data.S) { return nil, errors.New("invalid v, r, s values") } - hash := tx.Hash() - v, r, s := tx.GetSignatureValues() - sig := append(r, s...) - sig = append(sig, v-27) + // encode the signature in uncompressed format + r, s := tx.data.R.Bytes(), tx.data.S.Bytes() + sig := make([]byte, 65) + copy(sig[32-len(r):32], r) + copy(sig[64-len(s):64], s) + sig[64] = tx.data.V - 27 - p, err := crypto.SigToPub(hash[:], sig) + // recover the public key from the signature + hash := tx.Hash() + pub, err := crypto.Ecrecover(hash[:], sig) if err != nil { glog.V(logger.Error).Infof("Could not get pubkey from signature: ", err) return nil, err } - - pubkey := crypto.FromECDSAPub(p) - if len(pubkey) == 0 || pubkey[0] != 4 { + if len(pub) == 0 || pub[0] != 4 { return nil, errors.New("invalid public key") } - return pubkey, nil + return pub, nil } -func (tx *Transaction) SetSignatureValues(sig []byte) error { - tx.R = common.Bytes2Big(sig[:32]) - tx.S = common.Bytes2Big(sig[32:64]) - tx.V = sig[64] + 27 - return nil +func (tx *Transaction) WithSignature(sig []byte) (*Transaction, error) { + if len(sig) != 65 { + panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig))) + } + cpy := &Transaction{data: tx.data} + cpy.data.R = new(big.Int).SetBytes(sig[:32]) + cpy.data.S = new(big.Int).SetBytes(sig[32:64]) + cpy.data.V = sig[64] + 27 + return cpy, nil } -func (tx *Transaction) SignECDSA(prv *ecdsa.PrivateKey) error { +func (tx *Transaction) SignECDSA(prv *ecdsa.PrivateKey) (*Transaction, error) { h := tx.Hash() sig, err := crypto.Sign(h[:], prv) if err != nil { - return err + return nil, err } - tx.SetSignatureValues(sig) - return nil -} - -// TODO: remove -func (tx *Transaction) RlpData() interface{} { - data := []interface{}{tx.AccountNonce, tx.Price, tx.GasLimit, tx.Recipient, tx.Amount, tx.Payload} - return append(data, tx.V, tx.R.Bytes(), tx.S.Bytes()) + return tx.WithSignature(sig) } func (tx *Transaction) String() string { @@ -176,12 +219,12 @@ func (tx *Transaction) String() string { } else { from = fmt.Sprintf("%x", f[:]) } - if t := tx.To(); t == nil { + if tx.data.Recipient == nil { to = "[contract creation]" } else { - to = fmt.Sprintf("%x", t[:]) + to = fmt.Sprintf("%x", tx.data.Recipient[:]) } - enc, _ := rlp.EncodeToBytes(tx) + enc, _ := rlp.EncodeToBytes(&tx.data) return fmt.Sprintf(` TX(%x) Contract: %v @@ -198,36 +241,24 @@ func (tx *Transaction) String() string { Hex: %x `, tx.Hash(), - len(tx.Recipient) == 0, + len(tx.data.Recipient) == 0, from, to, - tx.AccountNonce, - tx.Price, - tx.GasLimit, - tx.Amount, - tx.Payload, - tx.V, - tx.R, - tx.S, + tx.data.AccountNonce, + tx.data.Price, + tx.data.GasLimit, + tx.data.Amount, + tx.data.Payload, + tx.data.V, + tx.data.R, + tx.data.S, enc, ) } -// Transaction slice type for basic sorting +// Transaction slice type for basic sorting. type Transactions []*Transaction -// TODO: remove -func (self Transactions) RlpData() interface{} { - // Marshal the transactions of this block - enc := make([]interface{}, len(self)) - for i, tx := range self { - // Cast it to a string (safe) - enc[i] = tx.RlpData() - } - - return enc -} - func (s Transactions) Len() int { return len(s) } func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] } @@ -239,5 +270,5 @@ func (s Transactions) GetRlp(i int) []byte { type TxByNonce struct{ Transactions } func (s TxByNonce) Less(i, j int) bool { - return s.Transactions[i].AccountNonce < s.Transactions[j].AccountNonce + return s.Transactions[i].data.AccountNonce < s.Transactions[j].data.AccountNonce } diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 492059c28..dd9c5e87b 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -15,40 +15,35 @@ import ( // at github.com/ethereum/tests. var ( - emptyTx = NewTransactionMessage( + emptyTx = NewTransaction( + 0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, ) - rightvrsRecipient = common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b") - rightvrsTx = &Transaction{ - Recipient: &rightvrsRecipient, - AccountNonce: 3, - Price: big.NewInt(1), - GasLimit: big.NewInt(2000), - Amount: big.NewInt(10), - Payload: common.FromHex("5544"), - V: 28, - R: common.String2Big("0x98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a"), - S: common.String2Big("0x8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3"), - } + rightvrsTx, _ = NewTransaction( + 3, + common.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b"), + big.NewInt(10), + big.NewInt(2000), + big.NewInt(1), + common.FromHex("5544"), + ).WithSignature( + common.Hex2Bytes("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"), + ) ) func TestTransactionHash(t *testing.T) { - // "EmptyTransaction" if emptyTx.Hash() != common.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") { t.Errorf("empty transaction hash mismatch, got %x", emptyTx.Hash()) } - - // "RightVRSTest" if rightvrsTx.Hash() != common.HexToHash("fe7a79529ed5f7c3375d06b26b186a8644e0e16c373d7a12be41c62d6042b77a") { t.Errorf("RightVRS transaction hash mismatch, got %x", rightvrsTx.Hash()) } } func TestTransactionEncode(t *testing.T) { - // "RightVRSTest" txb, err := rlp.EncodeToBytes(rightvrsTx) if err != nil { t.Fatalf("encode error: %v", err) @@ -72,19 +67,16 @@ func defaultTestKey() (*ecdsa.PrivateKey, common.Address) { func TestRecipientEmpty(t *testing.T) { _, addr := defaultTestKey() - tx, err := decodeTx(common.Hex2Bytes("f8498080808080011ca09b16de9d5bdee2cf56c28d16275a4da68cd30273e2525f3959f5d62557489921a0372ebd8fb3345f7db7b5a86d42e24d36e983e259b0664ceb8c227ec9af572f3d")) if err != nil { t.Error(err) t.FailNow() } - from, err := tx.From() if err != nil { t.Error(err) t.FailNow() } - if addr != from { t.Error("derived address doesn't match") } diff --git a/core/vm_env.go b/core/vm_env.go index da862d5c8..6dd83acde 100644 --- a/core/vm_env.go +++ b/core/vm_env.go @@ -10,32 +10,32 @@ import ( ) type VMEnv struct { - state *state.StateDB - block *types.Block - msg Message - depth int - chain *ChainManager - typ vm.Type + state *state.StateDB + header *types.Header + msg Message + depth int + chain *ChainManager + typ vm.Type // structured logging logs []vm.StructLog } -func NewEnv(state *state.StateDB, chain *ChainManager, msg Message, block *types.Block) *VMEnv { +func NewEnv(state *state.StateDB, chain *ChainManager, msg Message, header *types.Header) *VMEnv { return &VMEnv{ - chain: chain, - state: state, - block: block, - msg: msg, - typ: vm.StdVmTy, + chain: chain, + state: state, + header: header, + msg: msg, + typ: vm.StdVmTy, } } func (self *VMEnv) Origin() common.Address { f, _ := self.msg.From(); return f } -func (self *VMEnv) BlockNumber() *big.Int { return self.block.Number() } -func (self *VMEnv) Coinbase() common.Address { return self.block.Coinbase() } -func (self *VMEnv) Time() int64 { return self.block.Time() } -func (self *VMEnv) Difficulty() *big.Int { return self.block.Difficulty() } -func (self *VMEnv) GasLimit() *big.Int { return self.block.GasLimit() } +func (self *VMEnv) BlockNumber() *big.Int { return self.header.Number } +func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase } +func (self *VMEnv) Time() int64 { return int64(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) Depth() int { return self.depth } |