diff --git a/app/protocol/flows/v5/blockrelay/handle_request_past_diff.go b/app/protocol/flows/v5/blockrelay/handle_request_past_diff.go index 94378c1b2..46b393bdf 100644 --- a/app/protocol/flows/v5/blockrelay/handle_request_past_diff.go +++ b/app/protocol/flows/v5/blockrelay/handle_request_past_diff.go @@ -1,18 +1,20 @@ package blockrelay import ( + "github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/app/protocol/peer" "github.com/kaspanet/kaspad/app/protocol/protocolerrors" - "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - - "github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/domain" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/infrastructure/config" "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" + "sort" ) // RequestPastDiffContext is the interface for the context needed for the HandleRequestHeaders flow. type RequestPastDiffContext interface { Domain() domain.Domain + Config() *config.Config } type handleRequestPastDiffFlow struct { @@ -43,13 +45,13 @@ func (flow *handleRequestPastDiffFlow) start() error { log.Debugf("Received requestPastDiff with hasHash: %s, requestedHash: %s", hasHash, requestedHash) log.Debugf("Getting past(%s) setminus past(%s) to %s", requestedHash, hasHash, flow.peer) - // GetPastDiff is a relatively heavy operation so we limit it - // in order to avoid locking the consensus for too long - // maxBlocks MUST be >= MergeSetSizeLimit + 1 - const maxBlocks = 1 << 10 - blockHashes, err := flow.Domain().Consensus().GetPastDiff(hasHash, requestedHash, maxBlocks) + // GetPastDiff is expected to be called by the syncee for getting the anticone of the header selected tip + // intersected by past of relayed block, and is thus expected to be bounded by mergeset limit since + // we relay blocks only if they enter virtual's mergeset. We add 2 for a small margin error. + blockHashes, err := flow.Domain().Consensus().GetPastDiff(hasHash, requestedHash, + flow.Config().ActiveNetParams.MergeSetSizeLimit+2) if err != nil { - return protocolerrors.Wrap(true, err, "Expected hashes in anticone one of the other") + return protocolerrors.Wrap(true, err, "Failed querying anticone") } log.Debugf("Got %d header hashes in past(%s) setminus past(%s)", len(blockHashes), requestedHash, hasHash) @@ -62,6 +64,14 @@ func (flow *handleRequestPastDiffFlow) start() error { blockHeaders[i] = appmessage.DomainBlockHeaderToBlockHeader(blockHeader) } + // We sort the headers in bottom-up topological order before sending + sort.Slice(blockHeaders, func(i, j int) bool { + return blockHeaders[i].BlueWork.Cmp(blockHeaders[j].BlueWork) < 0 + }) + if err != nil { + return err + } + blockHeadersMessage := appmessage.NewBlockHeadersMessage(blockHeaders) err = flow.outgoingRoute.Enqueue(blockHeadersMessage) if err != nil { diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 5b74664dd..e4207c573 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -720,7 +720,7 @@ func (s *consensus) Anticone(blockHash *externalapi.DomainHash) ([]*externalapi. return nil, err } - return s.dagTraversalManager.AnticoneFromBlocks(stagingArea, tips, blockHash) + return s.dagTraversalManager.AnticoneFromBlocks(stagingArea, tips, blockHash, 0) } func (s *consensus) EstimateNetworkHashesPerSecond(startHash *externalapi.DomainHash, windowSize int) (uint64, error) { diff --git a/domain/consensus/model/errors.go b/domain/consensus/model/errors.go index a8d4a009e..1c48ab553 100644 --- a/domain/consensus/model/errors.go +++ b/domain/consensus/model/errors.go @@ -5,3 +5,7 @@ import "github.com/pkg/errors" // ErrBlockNotInSelectedParentChain is returned from CreateHeadersSelectedChainBlockLocator if one of the parameters // passed to it are not in the headers selected parent chain var ErrBlockNotInSelectedParentChain = errors.New("Block is not in selected parent chain") + +// ErrReachedMaxTraversalAllowed is returned from AnticoneFromBlocks if `maxTraversalAllowed` was specified +// and the traversal passed it +var ErrReachedMaxTraversalAllowed = errors.New("Traversal searching for anticone passed the maxTraversalAllowed limit") diff --git a/domain/consensus/model/interface_processes_dagtraversalmanager.go b/domain/consensus/model/interface_processes_dagtraversalmanager.go index 0e286e400..add161c2b 100644 --- a/domain/consensus/model/interface_processes_dagtraversalmanager.go +++ b/domain/consensus/model/interface_processes_dagtraversalmanager.go @@ -10,7 +10,7 @@ type DAGTraversalManager interface { // from lowHash (exclusive) to highHash (inclusive) over highHash's selected parent chain SelectedChildIterator(stagingArea *StagingArea, highHash, lowHash *externalapi.DomainHash, includeLowHash bool) (BlockIterator, error) SelectedChild(stagingArea *StagingArea, highHash, lowHash *externalapi.DomainHash) (*externalapi.DomainHash, error) - AnticoneFromBlocks(stagingArea *StagingArea, tips []*externalapi.DomainHash, blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) + AnticoneFromBlocks(stagingArea *StagingArea, tips []*externalapi.DomainHash, blockHash *externalapi.DomainHash, maxTraversalAllowed uint64) ([]*externalapi.DomainHash, error) AnticoneFromVirtualPOV(stagingArea *StagingArea, blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) BlockWindow(stagingArea *StagingArea, highHash *externalapi.DomainHash, windowSize int) ([]*externalapi.DomainHash, error) DAABlockWindow(stagingArea *StagingArea, highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) diff --git a/domain/consensus/processes/dagtraversalmanager/anticone.go b/domain/consensus/processes/dagtraversalmanager/anticone.go index d90fca465..1ba1954ac 100644 --- a/domain/consensus/processes/dagtraversalmanager/anticone.go +++ b/domain/consensus/processes/dagtraversalmanager/anticone.go @@ -14,16 +14,18 @@ func (dtm *dagTraversalManager) AnticoneFromVirtualPOV(stagingArea *model.Stagin return nil, err } - return dtm.AnticoneFromBlocks(stagingArea, virtualParents, blockHash) + return dtm.AnticoneFromBlocks(stagingArea, virtualParents, blockHash, 0) } -func (dtm *dagTraversalManager) AnticoneFromBlocks(stagingArea *model.StagingArea, tips []*externalapi.DomainHash, blockHash *externalapi.DomainHash) ( +func (dtm *dagTraversalManager) AnticoneFromBlocks(stagingArea *model.StagingArea, tips []*externalapi.DomainHash, + blockHash *externalapi.DomainHash, maxTraversalAllowed uint64) ( []*externalapi.DomainHash, error) { anticone := []*externalapi.DomainHash{} queue := tips visited := hashset.New() + traversalCounter := uint64(0) for len(queue) > 0 { var current *externalapi.DomainHash current, queue = queue[0], queue[1:] @@ -48,6 +50,13 @@ func (dtm *dagTraversalManager) AnticoneFromBlocks(stagingArea *model.StagingAre return nil, err } + // We count the number of blocks in past(tips) \setminus past(blockHash). + // We don't use `len(visited)` since it includes some maximal blocks in past(blockHash) as well. + traversalCounter++ + if maxTraversalAllowed > 0 && traversalCounter > maxTraversalAllowed { + return nil, model.ErrReachedMaxTraversalAllowed + } + if !blockIsAncestorOfCurrent { anticone = append(anticone, current) } diff --git a/domain/consensus/processes/syncmanager/syncmanager.go b/domain/consensus/processes/syncmanager/syncmanager.go index d35ea8f9a..01773b51d 100644 --- a/domain/consensus/processes/syncmanager/syncmanager.go +++ b/domain/consensus/processes/syncmanager/syncmanager.go @@ -92,8 +92,8 @@ func (sm *syncManager) GetPastDiff(stagingArea *model.StagingArea, hasHash, hasHash, requestedHash) } - // TODO: use maxBlocks limit - return sm.dagTraversalManager.AnticoneFromBlocks(stagingArea, []*externalapi.DomainHash{requestedHash}, hasHash) + return sm.dagTraversalManager.AnticoneFromBlocks(stagingArea, + []*externalapi.DomainHash{requestedHash}, hasHash, maxBlocks) } func (sm *syncManager) GetMissingBlockBodyHashes(stagingArea *model.StagingArea, highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {