[DEV-332] Create partial block message in wire and make sure partial nodes receive partial blocks (#170)

* [DEV-332] Added MerkleProof to MsgBlock and rejected full-node/partial-block type misbehaviors.

* [DEV-332] Fixed merge issues.

* [DEV-332] Got rid of MerkleProof. Turns out we no longer need it.

* [DEV-332] Got rid of NTBlockDisconnected, as no one was ever triggering it. (It was part of reorg)

* [DEV-332] Implemented clearing out the payloads of transactions of outgoing blocks for partial nodes.

* [DEV-332] Extracted ConvertToPartial to its own method. Added a test. Added a condition for converting to a partial block.

* [DEV-332] Fixed bad ConvertToPartial condition.
This commit is contained in:
stasatdaglabs 2019-01-23 14:51:05 +02:00 committed by Evgeny Khirin
parent b963c0d364
commit 349e62fcd5
6 changed files with 86 additions and 46 deletions

View File

@ -25,18 +25,13 @@ const (
// NTBlockConnected indicates the associated block was connected to the
// main chain.
NTBlockConnected
// NTBlockDisconnected indicates the associated block was disconnected
// from the main chain.
NTBlockDisconnected
)
// notificationTypeStrings is a map of notification types back to their constant
// names for pretty printing.
var notificationTypeStrings = map[NotificationType]string{
NTBlockAccepted: "NTBlockAccepted",
NTBlockConnected: "NTBlockConnected",
NTBlockDisconnected: "NTBlockDisconnected",
NTBlockAccepted: "NTBlockAccepted",
NTBlockConnected: "NTBlockConnected",
}
// String returns the NotificationType in human-readable form.

View File

@ -1210,32 +1210,6 @@ func (sm *SyncManager) handleBlockDAGNotification(notification *blockdag.Notific
mempool.DefaultEstimateFeeMinRegisteredBlocks)
}
}
// A block has been disconnected from the block DAG.
case blockdag.NTBlockDisconnected:
block, ok := notification.Data.(*util.Block)
if !ok {
log.Warnf("Chain disconnected notification is not a block.")
break
}
// Reinsert all of the transactions (except the coinbase) into
// the transaction pool.
for _, tx := range block.Transactions()[1:] {
_, _, err := sm.txMemPool.MaybeAcceptTransaction(tx,
false, false)
if err != nil {
// Remove the transaction and all transactions
// that depend on it if it wasn't accepted into
// the transaction pool.
sm.txMemPool.RemoveTransaction(tx, true, true)
}
}
// Rollback previous block recorded by the fee estimator.
if sm.feeEstimator != nil {
sm.feeEstimator.Rollback(block.Hash())
}
}
}

View File

@ -661,7 +661,7 @@ func (sp *Peer) OnGetData(_ *peer.Peer, msg *wire.MsgGetData) {
// OnGetBlocks is invoked when a peer receives a getblocks bitcoin
// message.
func (sp *Peer) OnGetBlocks(_ *peer.Peer, msg *wire.MsgGetBlocks) {
// Find the most recent known block in the best chain based on the block
// Find the most recent known block in the dag based on the block
// locator and fetch all of the block hashes after it until either
// wire.MaxBlocksPerMsg have been fetched or the provided stop hash is
// encountered.
@ -671,8 +671,8 @@ func (sp *Peer) OnGetBlocks(_ *peer.Peer, msg *wire.MsgGetBlocks) {
// over with the genesis block if unknown block locators are provided.
//
// This mirrors the behavior in the reference implementation.
chain := sp.server.DAG
hashList := chain.LocateBlocks(msg.BlockLocatorHashes, &msg.HashStop,
dag := sp.server.DAG
hashList := dag.LocateBlocks(msg.BlockLocatorHashes, &msg.HashStop,
wire.MaxBlocksPerMsg)
// Generate inventory message.
@ -1286,6 +1286,16 @@ func (s *Server) pushBlockMsg(sp *Peer, hash *daghash.Hash, doneChan chan<- stru
return err
}
// If we are a full node and the peer is a partial node, we must convert
// the block to a partial block.
nodeSubnetworkID := s.DAG.SubnetworkID()
peerSubnetworkID := sp.Peer.SubnetworkID()
isNodeFull := nodeSubnetworkID.IsEqual(&wire.SubnetworkIDSupportsAll)
isPeerFull := peerSubnetworkID.IsEqual(&wire.SubnetworkIDSupportsAll)
if isNodeFull && !isPeerFull {
msgBlock.ConvertToPartial(peerSubnetworkID)
}
// Once we have fetched data wait for any previous operation to finish.
if waitChan != nil {
<-waitChan

View File

@ -4300,16 +4300,6 @@ func (s *Server) handleBlockchainNotification(notification *blockdag.Notificatio
// Notify registered websocket clients of incoming block.
s.ntfnMgr.NotifyBlockConnected(block)
case blockdag.NTBlockDisconnected:
block, ok := notification.Data.(*util.Block)
if !ok {
log.Warnf("Chain disconnected notification is not a block.")
break
}
// Notify registered websocket clients.
s.ntfnMgr.NotifyBlockDisconnected(block)
}
}

View File

@ -7,6 +7,7 @@ package wire
import (
"bytes"
"fmt"
"github.com/daglabs/btcd/util/subnetworkid"
"io"
"github.com/daglabs/btcd/dagconfig/daghash"
@ -231,6 +232,17 @@ func (msg *MsgBlock) BlockHash() daghash.Hash {
return msg.Header.BlockHash()
}
// ConvertToPartial clears out all the payloads of the subnetworks that are
// incompatible with the given subnetwork ID.
// Note: this operation modifies the block in place.
func (msg *MsgBlock) ConvertToPartial(subnetworkID *subnetworkid.SubnetworkID) {
for _, tx := range msg.Transactions {
if !tx.SubnetworkID.IsEqual(subnetworkID) {
tx.Payload = []byte{}
}
}
}
// NewMsgBlock returns a new bitcoin block message that conforms to the
// Message interface. See MsgBlock for details.
func NewMsgBlock(blockHeader *BlockHeader) *MsgBlock {

View File

@ -6,6 +6,7 @@ package wire
import (
"bytes"
"github.com/daglabs/btcd/util/subnetworkid"
"io"
"math"
"reflect"
@ -86,6 +87,64 @@ func TestBlockHash(t *testing.T) {
}
}
func TestConvertToPartial(t *testing.T) {
transactions := []struct {
subnetworkID subnetworkid.SubnetworkID
payload []byte
expectedPayloadLength int
}{
{
subnetworkID: SubnetworkIDNative,
payload: []byte{},
expectedPayloadLength: 0,
},
{
subnetworkID: SubnetworkIDRegistry,
payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
expectedPayloadLength: 0,
},
{
subnetworkID: subnetworkid.SubnetworkID{123},
payload: []byte{0x01},
expectedPayloadLength: 1,
},
{
subnetworkID: subnetworkid.SubnetworkID{234},
payload: []byte{0x02},
expectedPayloadLength: 0,
},
}
block := MsgBlock{}
for _, transaction := range transactions {
block.Transactions = append(block.Transactions, &MsgTx{
SubnetworkID: transaction.subnetworkID,
Payload: []byte{1},
})
}
block.ConvertToPartial(&subnetworkid.SubnetworkID{123})
for _, transaction := range transactions {
var subnetworkTx *MsgTx
for _, tx := range block.Transactions {
if tx.SubnetworkID.IsEqual(&transaction.subnetworkID) {
subnetworkTx = tx
}
}
if subnetworkTx == nil {
t.Errorf("ConvertToPartial: subnetworkID '%s' not found in block!", transaction.subnetworkID)
continue
}
payloadLength := len(subnetworkTx.Payload)
if payloadLength != transaction.expectedPayloadLength {
t.Errorf("ConvertToPartial: unexpected payload length for subnetwork '%s': expected: %d, got: %d",
transaction.subnetworkID, transaction.expectedPayloadLength, payloadLength)
}
}
}
// TestBlockWire tests the MsgBlock wire encode and decode for various numbers
// of transaction inputs and outputs and protocol versions.
func TestBlockWire(t *testing.T) {