From d2f4ed660c9b765e922341bc452f33495e3a4e35 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Thu, 18 Feb 2021 10:39:12 +0200 Subject: [PATCH] Disallow header only blocks on RPC, relay and when requesting IBD full blocks (#1537) --- app/protocol/flowcontext/blocks.go | 5 +++ .../flows/blockrelay/handle_relay_invs.go | 14 +++++++ app/protocol/flows/blockrelay/ibd.go | 5 +++ .../flows/testing/handle_relay_invs_test.go | 42 +++++++++++++++++++ app/rpc/rpchandlers/submit_block.go | 4 +- 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/app/protocol/flowcontext/blocks.go b/app/protocol/flowcontext/blocks.go index 6eacd7dd2..3ddad114e 100644 --- a/app/protocol/flowcontext/blocks.go +++ b/app/protocol/flowcontext/blocks.go @@ -2,6 +2,7 @@ package flowcontext import ( peerpkg "github.com/kaspanet/kaspad/app/protocol/peer" + "github.com/kaspanet/kaspad/app/protocol/protocolerrors" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/pkg/errors" @@ -98,6 +99,10 @@ func (f *FlowContext) SharedRequestedBlocks() *blockrelay.SharedRequestedBlocks // AddBlock adds the given block to the DAG and propagates it. func (f *FlowContext) AddBlock(block *externalapi.DomainBlock) error { + if len(block.Transactions) == 0 { + return protocolerrors.Errorf(false, "cannot add header only block") + } + blockInsertionResult, err := f.Domain().Consensus().ValidateAndInsertBlock(block) if err != nil { if errors.As(err, &ruleerrors.RuleError{}) { diff --git a/app/protocol/flows/blockrelay/handle_relay_invs.go b/app/protocol/flows/blockrelay/handle_relay_invs.go index 9ac480c70..ff2bea87b 100644 --- a/app/protocol/flows/blockrelay/handle_relay_invs.go +++ b/app/protocol/flows/blockrelay/handle_relay_invs.go @@ -104,6 +104,11 @@ func (flow *handleRelayInvsFlow) start() error { continue } + err = flow.banIfBlockIsHeaderOnly(block) + if err != nil { + return err + } + log.Debugf("Processing block %s", inv.Hash) missingParents, blockInsertionResult, err := flow.processBlock(block) if err != nil { @@ -140,6 +145,15 @@ func (flow *handleRelayInvsFlow) start() error { } } +func (flow *handleRelayInvsFlow) banIfBlockIsHeaderOnly(block *externalapi.DomainBlock) error { + if len(block.Transactions) == 0 { + return protocolerrors.Errorf(true, "sent header of %s block where expected block with body", + consensushashing.BlockHash(block)) + } + + return nil +} + func (flow *handleRelayInvsFlow) readInv() (*appmessage.MsgInvRelayBlock, error) { if len(flow.invsQueue) > 0 { var inv *appmessage.MsgInvRelayBlock diff --git a/app/protocol/flows/blockrelay/ibd.go b/app/protocol/flows/blockrelay/ibd.go index e4851aed2..43a929796 100644 --- a/app/protocol/flows/blockrelay/ibd.go +++ b/app/protocol/flows/blockrelay/ibd.go @@ -533,6 +533,11 @@ func (flow *handleRelayInvsFlow) syncMissingBlockBodies(highHash *externalapi.Do return protocolerrors.Errorf(true, "expected block %s but got %s", expectedHash, blockHash) } + err = flow.banIfBlockIsHeaderOnly(block) + if err != nil { + return err + } + blockInsertionResult, err := flow.Domain().Consensus().ValidateAndInsertBlock(block) if err != nil { if errors.Is(err, ruleerrors.ErrDuplicateBlock) { diff --git a/app/protocol/flows/testing/handle_relay_invs_test.go b/app/protocol/flows/testing/handle_relay_invs_test.go index 36f28db13..e6fc2add7 100644 --- a/app/protocol/flows/testing/handle_relay_invs_test.go +++ b/app/protocol/flows/testing/handle_relay_invs_test.go @@ -24,6 +24,19 @@ import ( "github.com/pkg/errors" ) +var headerOnlyBlock = &externalapi.DomainBlock{ + Header: blockheader.NewImmutableBlockHeader( + constants.MaxBlockVersion, + []*externalapi.DomainHash{externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{1})}, + &externalapi.DomainHash{}, + &externalapi.DomainHash{}, + &externalapi.DomainHash{}, + 0, + 0, + 0, + ), +} + var orphanBlock = &externalapi.DomainBlock{ Header: blockheader.NewImmutableBlockHeader( constants.MaxBlockVersion, @@ -35,6 +48,7 @@ var orphanBlock = &externalapi.DomainBlock{ 0, 0, ), + Transactions: []*externalapi.DomainTransaction{{}}, } var validPruningPointBlock = &externalapi.DomainBlock{ @@ -48,6 +62,7 @@ var validPruningPointBlock = &externalapi.DomainBlock{ 0, 0, ), + Transactions: []*externalapi.DomainTransaction{{}}, } var invalidPruningPointBlock = &externalapi.DomainBlock{ @@ -61,6 +76,7 @@ var invalidPruningPointBlock = &externalapi.DomainBlock{ 0, 0, ), + Transactions: []*externalapi.DomainTransaction{{}}, } var unexpectedIBDBlock = &externalapi.DomainBlock{ @@ -74,6 +90,7 @@ var unexpectedIBDBlock = &externalapi.DomainBlock{ 0, 0, ), + Transactions: []*externalapi.DomainTransaction{{}}, } var invalidBlock = &externalapi.DomainBlock{ @@ -87,6 +104,7 @@ var invalidBlock = &externalapi.DomainBlock{ 0, 0, ), + Transactions: []*externalapi.DomainTransaction{{}}, } var unknownBlockHash = externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{1}) @@ -95,6 +113,7 @@ var validPruningPointHash = consensushashing.BlockHash(validPruningPointBlock) var invalidBlockHash = consensushashing.BlockHash(invalidBlock) var invalidPruningPointHash = consensushashing.BlockHash(invalidPruningPointBlock) var orphanBlockHash = consensushashing.BlockHash(orphanBlock) +var headerOnlyBlockHash = consensushashing.BlockHash(headerOnlyBlock) type fakeRelayInvsContext struct { testName string @@ -450,6 +469,29 @@ func TestHandleRelayInvs(t *testing.T) { expectsBan: true, expectsErrToContain: "got unrequested block", }, + { + name: "sending header only block on relay", + funcToExecute: func(t *testing.T, incomingRoute, outgoingRoute *router.Route, context *fakeRelayInvsContext) { + err := incomingRoute.Enqueue(appmessage.NewMsgInvBlock(headerOnlyBlockHash)) + if err != nil { + t.Fatalf("Enqueue: %+v", err) + } + + msg, err := outgoingRoute.DequeueWithTimeout(time.Second) + if err != nil { + t.Fatalf("DequeueWithTimeout: %+v", err) + } + _ = msg.(*appmessage.MsgRequestRelayBlocks) + + err = incomingRoute.Enqueue(appmessage.DomainBlockToMsgBlock(headerOnlyBlock)) + if err != nil { + t.Fatalf("Enqueue: %+v", err) + } + }, + expectsProtocolError: true, + expectsBan: true, + expectsErrToContain: "block where expected block with body", + }, { name: "sending invalid block", funcToExecute: func(t *testing.T, incomingRoute, outgoingRoute *router.Route, context *fakeRelayInvsContext) { diff --git a/app/rpc/rpchandlers/submit_block.go b/app/rpc/rpchandlers/submit_block.go index 475e2c00c..373eeb2f5 100644 --- a/app/rpc/rpchandlers/submit_block.go +++ b/app/rpc/rpchandlers/submit_block.go @@ -2,6 +2,7 @@ package rpchandlers import ( "github.com/kaspanet/kaspad/app/appmessage" + "github.com/kaspanet/kaspad/app/protocol/protocolerrors" "github.com/kaspanet/kaspad/app/rpc/rpccontext" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" @@ -25,9 +26,10 @@ func HandleSubmitBlock(context *rpccontext.Context, _ *router.Router, request ap err := context.ProtocolManager.AddBlock(domainBlock) if err != nil { - if !errors.As(err, &ruleerrors.RuleError{}) { + if !errors.As(err, &ruleerrors.RuleError{}) || !errors.As(err, &protocolerrors.ProtocolError{}) { return nil, err } + return &appmessage.SubmitBlockResponseMessage{ Error: appmessage.RPCErrorf("Block rejected. Reason: %s", err), RejectReason: appmessage.RejectReasonBlockInvalid,