From 349e62fcd5a074e5ed6969c42baff7c97eb24b16 Mon Sep 17 00:00:00 2001 From: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com> Date: Wed, 23 Jan 2019 14:51:05 +0200 Subject: [PATCH] [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. --- blockdag/notifications.go | 9 ++---- netsync/manager.go | 26 ----------------- server/p2p/p2p.go | 16 +++++++++-- server/rpc/rpcserver.go | 10 ------- wire/msgblock.go | 12 ++++++++ wire/msgblock_test.go | 59 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 46 deletions(-) diff --git a/blockdag/notifications.go b/blockdag/notifications.go index 7dcce2f66..83e997da8 100644 --- a/blockdag/notifications.go +++ b/blockdag/notifications.go @@ -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. diff --git a/netsync/manager.go b/netsync/manager.go index 6487a063f..9cc2793db 100644 --- a/netsync/manager.go +++ b/netsync/manager.go @@ -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()) - } } } diff --git a/server/p2p/p2p.go b/server/p2p/p2p.go index 9ede0fb51..c97466eaf 100644 --- a/server/p2p/p2p.go +++ b/server/p2p/p2p.go @@ -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 diff --git a/server/rpc/rpcserver.go b/server/rpc/rpcserver.go index c6033c849..d92932948 100644 --- a/server/rpc/rpcserver.go +++ b/server/rpc/rpcserver.go @@ -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) } } diff --git a/wire/msgblock.go b/wire/msgblock.go index 3f880f841..d6105798d 100644 --- a/wire/msgblock.go +++ b/wire/msgblock.go @@ -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 { diff --git a/wire/msgblock_test.go b/wire/msgblock_test.go index 63de594a8..59a223c5b 100644 --- a/wire/msgblock_test.go +++ b/wire/msgblock_test.go @@ -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) {