diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/block_processor.go | 13 | ||||
-rw-r--r-- | core/chain_makers.go | 142 | ||||
-rw-r--r-- | core/chain_manager_test.go | 194 | ||||
-rw-r--r-- | core/transaction_pool.go | 9 | ||||
-rw-r--r-- | core/types/block.go | 29 |
5 files changed, 378 insertions, 9 deletions
diff --git a/core/block_processor.go b/core/block_processor.go index 1a4501740..8974cd94b 100644 --- a/core/block_processor.go +++ b/core/block_processor.go @@ -12,7 +12,6 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/pow" - "github.com/ethereum/go-ethereum/pow/ezp" "github.com/ethereum/go-ethereum/state" "gopkg.in/fatih/set.v0" ) @@ -46,11 +45,11 @@ type BlockProcessor struct { eventMux *event.TypeMux } -func NewBlockProcessor(db ethutil.Database, txpool *TxPool, chainManager *ChainManager, eventMux *event.TypeMux) *BlockProcessor { +func NewBlockProcessor(db ethutil.Database, pow pow.PoW, txpool *TxPool, chainManager *ChainManager, eventMux *event.TypeMux) *BlockProcessor { sm := &BlockProcessor{ db: db, mem: make(map[string]*big.Int), - Pow: ezp.New(), + Pow: pow, bc: chainManager, eventMux: eventMux, txpool: txpool, @@ -105,6 +104,9 @@ func (self *BlockProcessor) ApplyTransaction(coinbase *state.StateObject, stated return receipt, txGas, err } +func (self *BlockProcessor) ChainManager() *ChainManager { + return self.bc +} func (self *BlockProcessor) ApplyTransactions(coinbase *state.StateObject, statedb *state.StateDB, block *types.Block, txs types.Transactions, transientProcess bool) (types.Receipts, types.Transactions, types.Transactions, types.Transactions, error) { var ( @@ -256,6 +258,11 @@ func (sm *BlockProcessor) ValidateBlock(block, parent *types.Block) error { return fmt.Errorf("GasLimit check failed for block %v (%v > %v)", block.Header().GasLimit, a, b) } + // There can be at most one uncle + if len(block.Uncles()) > 1 { + return ValidationError("Block can only contain one uncle (contained %v)", len(block.Uncles())) + } + if block.Time() < parent.Time() { return ValidationError("Block timestamp not after prev block (%v - %v)", block.Header().Time, parent.Header().Time) } diff --git a/core/chain_makers.go b/core/chain_makers.go new file mode 100644 index 000000000..7afdfde0d --- /dev/null +++ b/core/chain_makers.go @@ -0,0 +1,142 @@ +package core + +import ( + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethutil" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/pow" + "github.com/ethereum/go-ethereum/state" +) + +// So we can generate blocks easily +type FakePow struct{} + +func (f FakePow) Search(block pow.Block, stop <-chan struct{}) ([]byte, []byte, []byte) { + return nil, nil, nil +} +func (f FakePow) Verify(block pow.Block) bool { return true } +func (f FakePow) GetHashrate() int64 { return 0 } +func (f FakePow) Turbo(bool) {} + +// So we can deterministically seed different blockchains +var ( + 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 []byte, parent *types.Block) *types.Block { + return newBlockFromParent(addr, parent) +} + +func MakeBlock(bman *BlockProcessor, parent *types.Block, i int, db ethutil.Database, seed int) *types.Block { + return makeBlock(bman, parent, i, db, seed) +} + +func MakeChain(bman *BlockProcessor, parent *types.Block, max int, db ethutil.Database, seed int) types.Blocks { + return makeChain(bman, parent, max, db, seed) +} + +func NewChainMan(block *types.Block, eventMux *event.TypeMux, db ethutil.Database) *ChainManager { + return newChainManager(block, eventMux, db) +} + +func NewBlockProc(db ethutil.Database, txpool *TxPool, cman *ChainManager, eventMux *event.TypeMux) *BlockProcessor { + return newBlockProcessor(db, txpool, cman, eventMux) +} + +func NewCanonical(n int, db ethutil.Database) (*BlockProcessor, error) { + return newCanonical(n, db) +} + +// block time is fixed at 10 seconds +func newBlockFromParent(addr []byte, parent *types.Block) *types.Block { + block := types.NewBlock(parent.Hash(), addr, parent.Root(), ethutil.BigPow(2, 32), nil, "") + block.SetUncles(nil) + block.SetTransactions(nil) + block.SetReceipts(nil) + + header := block.Header() + header.Difficulty = CalcDifficulty(block, parent) + header.Number = new(big.Int).Add(parent.Header().Number, ethutil.Big1) + header.Time = parent.Header().Time + 10 + header.GasLimit = CalcGasLimit(parent, block) + + block.Td = parent.Td + + return block +} + +// 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 ethutil.Database, seed int) *types.Block { + addr := ethutil.LeftPadBytes([]byte{byte(i)}, 20) + addr[0] = byte(seed) + block := newBlockFromParent(addr, parent) + state := state.New(block.Root(), db) + cbase := state.GetOrNewStateObject(addr) + cbase.SetGasPool(CalcGasLimit(parent, block)) + cbase.AddBalance(BlockReward) + state.Update(ethutil.Big0) + block.SetRoot(state.Root()) + return block +} + +// Make a chain with real blocks +// Runs ProcessWithParent to get proper state roots +func makeChain(bman *BlockProcessor, parent *types.Block, max int, db ethutil.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) + td, err := bman.processWithParent(block, parent) + if err != nil { + fmt.Println("process with parent failed", err) + panic(err) + } + block.Td = td + blocks[i] = block + parent = block + } + return blocks +} + +// Create a new chain manager starting from given block +// Effectively a fork factory +func newChainManager(block *types.Block, eventMux *event.TypeMux, db ethutil.Database) *ChainManager { + bc := &ChainManager{db: db, genesisBlock: GenesisBlock(db), eventMux: eventMux} + if block == nil { + bc.Reset() + } else { + bc.currentBlock = block + bc.td = block.Td + } + return bc +} + +// block processor with fake pow +func newBlockProcessor(db ethutil.Database, txpool *TxPool, cman *ChainManager, eventMux *event.TypeMux) *BlockProcessor { + bman := NewBlockProcessor(db, FakePow{}, txpool, newChainManager(nil, eventMux, db), eventMux) + return bman +} + +// Make a new, deterministic canonical chain by running InsertChain +// on result of makeChain +func newCanonical(n int, db ethutil.Database) (*BlockProcessor, error) { + eventMux := &event.TypeMux{} + txpool := NewTxPool(eventMux) + + bman := newBlockProcessor(db, txpool, newChainManager(nil, eventMux, db), eventMux) + bman.bc.SetProcessor(bman) + parent := bman.bc.CurrentBlock() + if n == 0 { + return bman, nil + } + lchain := makeChain(bman, parent, n, db, CanonicalSeed) + err := bman.bc.InsertChain(lchain) + return bman, err +} diff --git a/core/chain_manager_test.go b/core/chain_manager_test.go index bc3a264d1..b562b677d 100644 --- a/core/chain_manager_test.go +++ b/core/chain_manager_test.go @@ -3,6 +3,7 @@ package core import ( "bytes" "fmt" + "math/big" "os" "path" "runtime" @@ -21,6 +22,75 @@ func init() { ethutil.ReadConfig("/tmp/ethtest", "/tmp/ethtest", "ETH") } +// Test fork of length N starting from block i +func testFork(t *testing.T, bman *BlockProcessor, i, N int, f func(td1, td2 *big.Int)) { + // switch databases to process the new chain + db, err := ethdb.NewMemDatabase() + if err != nil { + t.Fatal("Failed to create db:", err) + } + // copy old chain up to i into new db with deterministic canonical + bman2, err := newCanonical(i, db) + if err != nil { + t.Fatal("could not make new canonical in testFork", err) + } + // asert the bmans have the same block at i + bi1 := bman.bc.GetBlockByNumber(uint64(i)).Hash() + bi2 := bman2.bc.GetBlockByNumber(uint64(i)).Hash() + if bytes.Compare(bi1, bi2) != 0 { + 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) + err = bman2.bc.InsertChain(chainB) + if err != nil { + t.Fatal("Insert chain error for fork:", err) + } + + tdpre := bman.bc.Td() + // Test the fork's blocks on the original chain + td, err := testChain(chainB, bman) + if err != nil { + t.Fatal("expected chainB not to give errors:", err) + } + // Compare difficulties + f(tdpre, td) +} + +func printChain(bc *ChainManager) { + for i := bc.CurrentBlock().Number().Uint64(); i > 0; i-- { + b := bc.GetBlockByNumber(uint64(i)) + fmt.Printf("\t%x\n", b.Hash()) + } +} + +// process blocks against a chain +func testChain(chainB types.Blocks, bman *BlockProcessor) (*big.Int, error) { + td := new(big.Int) + for _, block := range chainB { + td2, err := bman.bc.processor.Process(block) + if err != nil { + if IsKnownBlockErr(err) { + continue + } + return nil, err + } + block.Td = td2 + td = td2 + + bman.bc.mu.Lock() + { + bman.bc.write(block) + } + bman.bc.mu.Unlock() + } + return td, nil +} + func loadChain(fn string, t *testing.T) (types.Blocks, error) { fh, err := os.OpenFile(path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "_data", fn), os.O_RDONLY, os.ModePerm) if err != nil { @@ -45,6 +115,130 @@ func insertChain(done chan bool, chainMan *ChainManager, chain types.Blocks, t * done <- true } +func TestExtendCanonical(t *testing.T) { + CanonicalLength := 5 + db, err := ethdb.NewMemDatabase() + if err != nil { + t.Fatal("Failed to create db:", err) + } + // make first chain starting from genesis + bman, err := newCanonical(CanonicalLength, db) + if err != nil { + t.Fatal("Could not make new canonical chain:", err) + } + f := func(td1, td2 *big.Int) { + if td2.Cmp(td1) <= 0 { + t.Error("expected chainB to have higher difficulty. Got", td2, "expected more than", td1) + } + } + // Start fork from current height (CanonicalLength) + testFork(t, bman, CanonicalLength, 1, f) + testFork(t, bman, CanonicalLength, 2, f) + testFork(t, bman, CanonicalLength, 5, f) + testFork(t, bman, CanonicalLength, 10, f) +} + +func TestShorterFork(t *testing.T) { + db, err := ethdb.NewMemDatabase() + if err != nil { + t.Fatal("Failed to create db:", err) + } + // make first chain starting from genesis + bman, err := newCanonical(10, db) + if err != nil { + t.Fatal("Could not make new canonical chain:", err) + } + f := func(td1, td2 *big.Int) { + if td2.Cmp(td1) >= 0 { + t.Error("expected chainB to have lower difficulty. Got", td2, "expected less than", td1) + } + } + // Sum of numbers must be less than 10 + // for this to be a shorter fork + testFork(t, bman, 0, 3, f) + testFork(t, bman, 0, 7, f) + testFork(t, bman, 1, 1, f) + testFork(t, bman, 1, 7, f) + testFork(t, bman, 5, 3, f) + testFork(t, bman, 5, 4, f) +} + +func TestLongerFork(t *testing.T) { + db, err := ethdb.NewMemDatabase() + if err != nil { + t.Fatal("Failed to create db:", err) + } + // make first chain starting from genesis + bman, err := newCanonical(10, db) + if err != nil { + t.Fatal("Could not make new canonical chain:", err) + } + f := func(td1, td2 *big.Int) { + if td2.Cmp(td1) <= 0 { + t.Error("expected chainB to have higher difficulty. Got", td2, "expected more than", td1) + } + } + // Sum of numbers must be greater than 10 + // for this to be a longer fork + testFork(t, bman, 0, 11, f) + testFork(t, bman, 0, 15, f) + testFork(t, bman, 1, 10, f) + testFork(t, bman, 1, 12, f) + testFork(t, bman, 5, 6, f) + testFork(t, bman, 5, 8, f) +} + +func TestEqualFork(t *testing.T) { + db, err := ethdb.NewMemDatabase() + if err != nil { + t.Fatal("Failed to create db:", err) + } + bman, err := newCanonical(10, db) + if err != nil { + t.Fatal("Could not make new canonical chain:", err) + } + f := func(td1, td2 *big.Int) { + if td2.Cmp(td1) != 0 { + t.Error("expected chainB to have equal difficulty. Got", td2, "expected ", td1) + } + } + // Sum of numbers must be equal to 10 + // for this to be an equal fork + testFork(t, bman, 0, 10, f) + testFork(t, bman, 1, 9, f) + testFork(t, bman, 2, 8, f) + testFork(t, bman, 5, 5, f) + testFork(t, bman, 6, 4, f) + testFork(t, bman, 9, 1, f) +} + +func TestBrokenChain(t *testing.T) { + db, err := ethdb.NewMemDatabase() + if err != nil { + t.Fatal("Failed to create db:", err) + } + bman, err := newCanonical(10, db) + if err != nil { + t.Fatal("Could not make new canonical chain:", err) + } + db2, err := ethdb.NewMemDatabase() + if err != nil { + t.Fatal("Failed to create db:", err) + } + bman2, err := newCanonical(10, db2) + if err != nil { + t.Fatal("Could not make new canonical chain:", err) + } + bman2.bc.SetProcessor(bman2) + parent := bman2.bc.CurrentBlock() + chainB := makeChain(bman2, parent, 5, db2, ForkSeed) + chainB = chainB[1:] + _, err = testChain(chainB, bman) + if err == nil { + t.Error("expected broken chain to return error") + } +} + func TestChainInsertions(t *testing.T) { t.Skip() // travil fails. diff --git a/core/transaction_pool.go b/core/transaction_pool.go index 050cff3d8..bd377f679 100644 --- a/core/transaction_pool.go +++ b/core/transaction_pool.go @@ -117,8 +117,13 @@ func (self *TxPool) add(tx *types.Transaction) error { } else { to = "[NEW_CONTRACT]" } - - txplogger.Debugf("(t) %x => %s (%v) %x\n", tx.From()[:4], to, tx.Value, tx.Hash()) + var from string + if len(tx.From()) > 0 { + from = ethutil.Bytes2Hex(tx.From()[:4]) + } else { + return errors.New(fmt.Sprintf("FROM ADDRESS MUST BE POSITIVE (was %v)", tx.From())) + } + txplogger.Debugf("(t) %x => %s (%v) %x\n", from, to, tx.Value, tx.Hash()) // Notify the subscribers go self.eventMux.Post(TxPreEvent{tx}) diff --git a/core/types/block.go b/core/types/block.go index d57de1311..673c72003 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -41,12 +41,29 @@ type Header struct { Extra string // Block Nonce for verification Nonce ethutil.Bytes + // Mix digest for quick checking to prevent DOS + MixDigest ethutil.Bytes + // SeedHash used for light client verification + SeedHash ethutil.Bytes } 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} + 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.Nonce) + fields = append(fields, self.Nonce, self.MixDigest, self.SeedHash) } return fields @@ -176,6 +193,8 @@ func (self *Block) RlpDataForStorage() interface{} { // 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() []byte { return self.header.MixDigest } +func (self *Block) SeedHash() []byte { return self.header.SeedHash } func (self *Block) Nonce() []byte { return self.header.Nonce } func (self *Block) Bloom() []byte { return self.header.Bloom } func (self *Block) Coinbase() []byte { return self.header.Coinbase } @@ -200,7 +219,6 @@ func (self *Block) GetUncle(i int) *Header { // Implement pow.Block func (self *Block) Difficulty() *big.Int { return self.header.Difficulty } -func (self *Block) N() []byte { return self.header.Nonce } func (self *Block) HashNoNonce() []byte { return self.header.HashNoNonce() } func (self *Block) Hash() []byte { @@ -250,7 +268,10 @@ func (self *Header) String() string { Time: %v Extra: %v Nonce: %x -`, 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.Nonce) + MixDigest: %x + SeedHash: %x + +`, 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.Nonce, self.MixDigest, self.SeedHash) } type Blocks []*Block |