diff options
Diffstat (limited to 'eth')
-rw-r--r-- | eth/backend.go | 2 | ||||
-rw-r--r-- | eth/backend_test.go | 2 | ||||
-rw-r--r-- | eth/db_upgrade.go | 13 | ||||
-rw-r--r-- | eth/downloader/downloader_test.go | 2 | ||||
-rw-r--r-- | eth/fetcher/fetcher_test.go | 2 | ||||
-rw-r--r-- | eth/filters/filter_test.go | 4 | ||||
-rw-r--r-- | eth/handler.go | 58 | ||||
-rw-r--r-- | eth/handler_test.go | 74 | ||||
-rw-r--r-- | eth/helper_test.go | 2 | ||||
-rw-r--r-- | eth/peer.go | 10 |
10 files changed, 156 insertions, 13 deletions
diff --git a/eth/backend.go b/eth/backend.go index 351cc2744..c8a9af6ee 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -205,6 +205,8 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { if config.ChainConfig == nil { return nil, errors.New("missing chain config") } + core.WriteChainConfig(chainDb, genesis.Hash(), config.ChainConfig) + eth.chainConfig = config.ChainConfig eth.chainConfig.VmConfig = vm.Config{ EnableJit: config.EnableJit, diff --git a/eth/backend_test.go b/eth/backend_test.go index cb94adbf0..105d71080 100644 --- a/eth/backend_test.go +++ b/eth/backend_test.go @@ -32,7 +32,7 @@ func TestMipmapUpgrade(t *testing.T) { addr := common.BytesToAddress([]byte("jeff")) genesis := core.WriteGenesisBlockForTesting(db) - chain, receipts := core.GenerateChain(genesis, db, 10, func(i int, gen *core.BlockGen) { + chain, receipts := core.GenerateChain(nil, genesis, db, 10, func(i int, gen *core.BlockGen) { var receipts types.Receipts switch i { case 1: diff --git a/eth/db_upgrade.go b/eth/db_upgrade.go index 12de60fe7..172bb0954 100644 --- a/eth/db_upgrade.go +++ b/eth/db_upgrade.go @@ -93,6 +93,9 @@ func upgradeSequentialKeys(db ethdb.Database) (stopFn func()) { func upgradeSequentialCanonicalNumbers(db ethdb.Database, stopFn func() bool) (error, bool) { prefix := []byte("block-num-") it := db.(*ethdb.LDBDatabase).NewIterator() + defer func() { + it.Release() + }() it.Seek(prefix) cnt := 0 for bytes.HasPrefix(it.Key(), prefix) { @@ -100,6 +103,9 @@ func upgradeSequentialCanonicalNumbers(db ethdb.Database, stopFn func() bool) (e if len(keyPtr) < 20 { cnt++ if cnt%100000 == 0 { + it.Release() + it = db.(*ethdb.LDBDatabase).NewIterator() + it.Seek(keyPtr) glog.V(logger.Info).Infof("converting %d canonical numbers...", cnt) } number := big.NewInt(0).SetBytes(keyPtr[10:]).Uint64() @@ -130,6 +136,9 @@ func upgradeSequentialCanonicalNumbers(db ethdb.Database, stopFn func() bool) (e func upgradeSequentialBlocks(db ethdb.Database, stopFn func() bool) (error, bool) { prefix := []byte("block-") it := db.(*ethdb.LDBDatabase).NewIterator() + defer func() { + it.Release() + }() it.Seek(prefix) cnt := 0 for bytes.HasPrefix(it.Key(), prefix) { @@ -137,6 +146,9 @@ func upgradeSequentialBlocks(db ethdb.Database, stopFn func() bool) (error, bool if len(keyPtr) >= 38 { cnt++ if cnt%10000 == 0 { + it.Release() + it = db.(*ethdb.LDBDatabase).NewIterator() + it.Seek(keyPtr) glog.V(logger.Info).Infof("converting %d blocks...", cnt) } // convert header, body, td and block receipts @@ -175,6 +187,7 @@ func upgradeSequentialBlocks(db ethdb.Database, stopFn func() bool) (error, bool func upgradeSequentialOrphanedReceipts(db ethdb.Database, stopFn func() bool) (error, bool) { prefix := []byte("receipts-block-") it := db.(*ethdb.LDBDatabase).NewIterator() + defer it.Release() it.Seek(prefix) cnt := 0 for bytes.HasPrefix(it.Key(), prefix) { diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index e9e051ded..fac6ef81c 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -55,7 +55,7 @@ func init() { // reassembly. func makeChain(n int, seed byte, parent *types.Block, parentReceipts types.Receipts, heavy bool) ([]common.Hash, map[common.Hash]*types.Header, map[common.Hash]*types.Block, map[common.Hash]types.Receipts) { // Generate the block chain - blocks, receipts := core.GenerateChain(parent, testdb, n, func(i int, block *core.BlockGen) { + blocks, receipts := core.GenerateChain(nil, parent, testdb, n, func(i int, block *core.BlockGen) { block.SetCoinbase(common.Address{seed}) // If a heavy chain is requested, delay blocks to raise difficulty diff --git a/eth/fetcher/fetcher_test.go b/eth/fetcher/fetcher_test.go index 2404c8cfa..6a32be14c 100644 --- a/eth/fetcher/fetcher_test.go +++ b/eth/fetcher/fetcher_test.go @@ -45,7 +45,7 @@ var ( // contains a transaction and every 5th an uncle to allow testing correct block // reassembly. func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) { - blocks, _ := core.GenerateChain(parent, testdb, n, func(i int, block *core.BlockGen) { + blocks, _ := core.GenerateChain(nil, parent, testdb, n, func(i int, block *core.BlockGen) { block.SetCoinbase(common.Address{seed}) // If the block number is multiple of 3, send a bonus transaction to the miner diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index a95adfce7..7b714f5d5 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -57,7 +57,7 @@ func BenchmarkMipmaps(b *testing.B) { defer db.Close() genesis := core.WriteGenesisBlockForTesting(db, core.GenesisAccount{Address: addr1, Balance: big.NewInt(1000000)}) - chain, receipts := core.GenerateChain(genesis, db, 100010, func(i int, gen *core.BlockGen) { + chain, receipts := core.GenerateChain(nil, genesis, db, 100010, func(i int, gen *core.BlockGen) { var receipts types.Receipts switch i { case 2403: @@ -133,7 +133,7 @@ func TestFilters(t *testing.T) { defer db.Close() genesis := core.WriteGenesisBlockForTesting(db, core.GenesisAccount{Address: addr, Balance: big.NewInt(1000000)}) - chain, receipts := core.GenerateChain(genesis, db, 1000, func(i int, gen *core.BlockGen) { + chain, receipts := core.GenerateChain(nil, genesis, db, 1000, func(i int, gen *core.BlockGen) { var receipts types.Receipts switch i { case 1: diff --git a/eth/handler.go b/eth/handler.go index 47a36cc0b..01550e6c2 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -45,6 +45,10 @@ const ( estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header ) +var ( + daoChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the DAO handshake challenge +) + // errIncompatibleConfig is returned if the requested protocols and configs are // not compatible (low protocol version restrictions and high requirements). var errIncompatibleConfig = errors.New("incompatible configuration") @@ -62,9 +66,10 @@ type ProtocolManager struct { fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks) synced uint32 // Flag whether we're considered synchronised (enables transaction processing) - txpool txPool - blockchain *core.BlockChain - chaindb ethdb.Database + txpool txPool + blockchain *core.BlockChain + chaindb ethdb.Database + chainconfig *core.ChainConfig downloader *downloader.Downloader fetcher *fetcher.Fetcher @@ -99,6 +104,7 @@ func NewProtocolManager(config *core.ChainConfig, fastSync bool, networkId int, txpool: txpool, blockchain: blockchain, chaindb: chaindb, + chainconfig: config, peers: newPeerSet(), newPeerCh: make(chan *peer), noMorePeers: make(chan struct{}), @@ -278,6 +284,18 @@ func (pm *ProtocolManager) handle(p *peer) error { // after this will be sent via broadcasts. pm.syncTransactions(p) + // If we're DAO hard-fork aware, validate any remote peer with regard to the hard-fork + if daoBlock := pm.chainconfig.DAOForkBlock; daoBlock != nil { + // Request the peer's DAO fork header for extra-data validation + if err := p.RequestHeadersByNumber(daoBlock.Uint64(), 1, 0, false); err != nil { + return err + } + // Start a timer to disconnect if the peer doesn't reply in time + p.forkDrop = time.AfterFunc(daoChallengeTimeout, func() { + glog.V(logger.Warn).Infof("%v: timed out DAO fork-check, dropping", p) + pm.removePeer(p.id) + }) + } // main loop. handle incoming messages. for { if err := pm.handleMsg(p); err != nil { @@ -481,9 +499,43 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { if err := msg.Decode(&headers); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } + // If no headers were received, but we're expending a DAO fork check, maybe it's that + if len(headers) == 0 && p.forkDrop != nil { + // Possibly an empty reply to the fork header checks, sanity check TDs + verifyDAO := true + + // If we already have a DAO header, we can check the peer's TD against it. If + // the peer's ahead of this, it too must have a reply to the DAO check + if daoHeader := pm.blockchain.GetHeaderByNumber(pm.chainconfig.DAOForkBlock.Uint64()); daoHeader != nil { + if p.Td().Cmp(pm.blockchain.GetTd(daoHeader.Hash(), daoHeader.Number.Uint64())) >= 0 { + verifyDAO = false + } + } + // If we're seemingly on the same chain, disable the drop timer + if verifyDAO { + glog.V(logger.Debug).Infof("%v: seems to be on the same side of the DAO fork", p) + p.forkDrop.Stop() + p.forkDrop = nil + return nil + } + } // Filter out any explicitly requested headers, deliver the rest to the downloader filter := len(headers) == 1 if filter { + // If it's a potential DAO fork check, validate against the rules + if p.forkDrop != nil && pm.chainconfig.DAOForkBlock.Cmp(headers[0].Number) == 0 { + // Disable the fork drop timer + p.forkDrop.Stop() + p.forkDrop = nil + + // Validate the header and either drop the peer or continue + if err := core.ValidateDAOHeaderExtraData(pm.chainconfig, headers[0]); err != nil { + glog.V(logger.Debug).Infof("%v: verified to be on the other side of the DAO fork, dropping", p) + return err + } + glog.V(logger.Debug).Infof("%v: verified to be on the same side of the DAO fork", p) + } + // Irrelevant of the fork checks, send the header to the fetcher just in case headers = pm.fetcher.FilterHeaders(headers, time.Now()) } if len(headers) > 0 || !filter { diff --git a/eth/handler_test.go b/eth/handler_test.go index 8418c28b2..66ff26809 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -20,6 +20,7 @@ import ( "math/big" "math/rand" "testing" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -28,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" ) @@ -580,3 +582,75 @@ func testGetReceipt(t *testing.T, protocol int) { t.Errorf("receipts mismatch: %v", err) } } + +// Tests that post eth protocol handshake, DAO fork-enabled clients also execute +// a DAO "challenge" verifying each others' DAO fork headers to ensure they're on +// compatible chains. +func TestDAOChallengeNoVsNo(t *testing.T) { testDAOChallenge(t, false, false, false) } +func TestDAOChallengeNoVsPro(t *testing.T) { testDAOChallenge(t, false, true, false) } +func TestDAOChallengeProVsNo(t *testing.T) { testDAOChallenge(t, true, false, false) } +func TestDAOChallengeProVsPro(t *testing.T) { testDAOChallenge(t, true, true, false) } +func TestDAOChallengeNoVsTimeout(t *testing.T) { testDAOChallenge(t, false, false, true) } +func TestDAOChallengeProVsTimeout(t *testing.T) { testDAOChallenge(t, true, true, true) } + +func testDAOChallenge(t *testing.T, localForked, remoteForked bool, timeout bool) { + // Reduce the DAO handshake challenge timeout + if timeout { + defer func(old time.Duration) { daoChallengeTimeout = old }(daoChallengeTimeout) + daoChallengeTimeout = 500 * time.Millisecond + } + // Create a DAO aware protocol manager + var ( + evmux = new(event.TypeMux) + pow = new(core.FakePow) + db, _ = ethdb.NewMemDatabase() + genesis = core.WriteGenesisBlockForTesting(db) + config = &core.ChainConfig{DAOForkBlock: big.NewInt(1), DAOForkSupport: localForked} + blockchain, _ = core.NewBlockChain(db, config, pow, evmux) + ) + pm, err := NewProtocolManager(config, false, NetworkId, evmux, new(testTxPool), pow, blockchain, db) + if err != nil { + t.Fatalf("failed to start test protocol manager: %v", err) + } + pm.Start() + defer pm.Stop() + + // Connect a new peer and check that we receive the DAO challenge + peer, _ := newTestPeer("peer", eth63, pm, true) + defer peer.close() + + challenge := &getBlockHeadersData{ + Origin: hashOrNumber{Number: config.DAOForkBlock.Uint64()}, + Amount: 1, + Skip: 0, + Reverse: false, + } + if err := p2p.ExpectMsg(peer.app, GetBlockHeadersMsg, challenge); err != nil { + t.Fatalf("challenge mismatch: %v", err) + } + // Create a block to reply to the challenge if no timeout is simualted + if !timeout { + blocks, _ := core.GenerateChain(nil, genesis, db, 1, func(i int, block *core.BlockGen) { + if remoteForked { + block.SetExtra(params.DAOForkBlockExtra) + } + }) + if err := p2p.Send(peer.app, BlockHeadersMsg, []*types.Header{blocks[0].Header()}); err != nil { + t.Fatalf("failed to answer challenge: %v", err) + } + time.Sleep(100 * time.Millisecond) // Sleep to avoid the verification racing with the drops + } else { + // Otherwise wait until the test timeout passes + time.Sleep(daoChallengeTimeout + 500*time.Millisecond) + } + // Verify that depending on fork side, the remote peer is maintained or dropped + if localForked == remoteForked && !timeout { + if peers := pm.peers.Len(); peers != 1 { + t.Fatalf("peer count mismatch: have %d, want %d", peers, 1) + } + } else { + if peers := pm.peers.Len(); peers != 0 { + t.Fatalf("peer count mismatch: have %d, want %d", peers, 0) + } + } +} diff --git a/eth/helper_test.go b/eth/helper_test.go index dacb1593f..28ff69b17 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -56,7 +56,7 @@ func newTestProtocolManager(fastSync bool, blocks int, generator func(int, *core chainConfig = &core.ChainConfig{HomesteadBlock: big.NewInt(0)} // homestead set to 0 because of chain maker blockchain, _ = core.NewBlockChain(db, chainConfig, pow, evmux) ) - chain, _ := core.GenerateChain(genesis, db, blocks, generator) + chain, _ := core.GenerateChain(nil, genesis, db, blocks, generator) if _, err := blockchain.InsertChain(chain); err != nil { panic(err) } diff --git a/eth/peer.go b/eth/peer.go index 8eb41b0f9..b97825c69 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -59,10 +59,12 @@ type peer struct { *p2p.Peer rw p2p.MsgReadWriter - version int // Protocol version negotiated - head common.Hash - td *big.Int - lock sync.RWMutex + version int // Protocol version negotiated + forkDrop *time.Timer // Timed connection dropper if forks aren't validated in time + + head common.Hash + td *big.Int + lock sync.RWMutex knownTxs *set.Set // Set of transaction hashes known to be known by this peer knownBlocks *set.Set // Set of block hashes known to be known by this peer |