core, eth: enforce network split post DAO hard-fork
This commit is contained in:
parent
a87089fd2d
commit
7f00e8c033
|
@ -248,6 +248,13 @@ func ValidateHeader(config *ChainConfig, pow pow.PoW, header *types.Header, pare
|
||||||
return &BlockNonceErr{header.Number, header.Hash(), header.Nonce.Uint64()}
|
return &BlockNonceErr{header.Number, header.Hash(), header.Nonce.Uint64()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If all checks passed, validate the extra-data field for hard forks
|
||||||
|
return ValidateHeaderExtraData(config, header)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateHeaderExtraData validates the extra-data field of a block header to
|
||||||
|
// ensure it conforms to hard-fork rules.
|
||||||
|
func ValidateHeaderExtraData(config *ChainConfig, header *types.Header) error {
|
||||||
// DAO hard-fork extension to the header validity: a) if the node is no-fork,
|
// DAO hard-fork extension to the header validity: a) if the node is no-fork,
|
||||||
// do not accept blocks in the [fork, fork+10) range with the fork specific
|
// do not accept blocks in the [fork, fork+10) range with the fork specific
|
||||||
// extra-data set; b) if the node is pro-fork, require blocks in the specific
|
// extra-data set; b) if the node is pro-fork, require blocks in the specific
|
||||||
|
|
|
@ -45,6 +45,10 @@ const (
|
||||||
estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header
|
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
|
// errIncompatibleConfig is returned if the requested protocols and configs are
|
||||||
// not compatible (low protocol version restrictions and high requirements).
|
// not compatible (low protocol version restrictions and high requirements).
|
||||||
var errIncompatibleConfig = errors.New("incompatible configuration")
|
var errIncompatibleConfig = errors.New("incompatible configuration")
|
||||||
|
@ -65,6 +69,7 @@ type ProtocolManager struct {
|
||||||
txpool txPool
|
txpool txPool
|
||||||
blockchain *core.BlockChain
|
blockchain *core.BlockChain
|
||||||
chaindb ethdb.Database
|
chaindb ethdb.Database
|
||||||
|
chainconfig *core.ChainConfig
|
||||||
|
|
||||||
downloader *downloader.Downloader
|
downloader *downloader.Downloader
|
||||||
fetcher *fetcher.Fetcher
|
fetcher *fetcher.Fetcher
|
||||||
|
@ -99,6 +104,7 @@ func NewProtocolManager(config *core.ChainConfig, fastSync bool, networkId int,
|
||||||
txpool: txpool,
|
txpool: txpool,
|
||||||
blockchain: blockchain,
|
blockchain: blockchain,
|
||||||
chaindb: chaindb,
|
chaindb: chaindb,
|
||||||
|
chainconfig: config,
|
||||||
peers: newPeerSet(),
|
peers: newPeerSet(),
|
||||||
newPeerCh: make(chan *peer),
|
newPeerCh: make(chan *peer),
|
||||||
noMorePeers: make(chan struct{}),
|
noMorePeers: make(chan struct{}),
|
||||||
|
@ -278,6 +284,18 @@ func (pm *ProtocolManager) handle(p *peer) error {
|
||||||
// after this will be sent via broadcasts.
|
// after this will be sent via broadcasts.
|
||||||
pm.syncTransactions(p)
|
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.
|
// main loop. handle incoming messages.
|
||||||
for {
|
for {
|
||||||
if err := pm.handleMsg(p); err != nil {
|
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 {
|
if err := msg.Decode(&headers); err != nil {
|
||||||
return errResp(ErrDecode, "msg %v: %v", msg, err)
|
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.Info).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 out any explicitly requested headers, deliver the rest to the downloader
|
||||||
filter := len(headers) == 1
|
filter := len(headers) == 1
|
||||||
if filter {
|
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.ValidateHeaderExtraData(pm.chainconfig, headers[0]); err != nil {
|
||||||
|
glog.V(logger.Info).Infof("%v: verified to be on the other side of the DAO fork, dropping", p)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
glog.V(logger.Info).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())
|
headers = pm.fetcher.FilterHeaders(headers, time.Now())
|
||||||
}
|
}
|
||||||
if len(headers) > 0 || !filter {
|
if len(headers) > 0 || !filter {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
@ -28,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
@ -580,3 +582,74 @@ func testGetReceipt(t *testing.T, protocol int) {
|
||||||
t.Errorf("receipts mismatch: %v", err)
|
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(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)
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -60,6 +60,8 @@ type peer struct {
|
||||||
rw p2p.MsgReadWriter
|
rw p2p.MsgReadWriter
|
||||||
|
|
||||||
version int // Protocol version negotiated
|
version int // Protocol version negotiated
|
||||||
|
forkDrop *time.Timer // Timed connection dropper if forks aren't validated in time
|
||||||
|
|
||||||
head common.Hash
|
head common.Hash
|
||||||
td *big.Int
|
td *big.Int
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
|
Loading…
Reference in New Issue