aboutsummaryrefslogtreecommitdiffstats
path: root/eth
diff options
context:
space:
mode:
Diffstat (limited to 'eth')
-rw-r--r--eth/backend.go2
-rw-r--r--eth/backend_test.go2
-rw-r--r--eth/db_upgrade.go13
-rw-r--r--eth/downloader/downloader_test.go2
-rw-r--r--eth/fetcher/fetcher_test.go2
-rw-r--r--eth/filters/filter_test.go4
-rw-r--r--eth/handler.go58
-rw-r--r--eth/handler_test.go74
-rw-r--r--eth/helper_test.go2
-rw-r--r--eth/peer.go10
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