diff --git a/app/protocol/flows/blockrelay/ibd.go b/app/protocol/flows/blockrelay/ibd.go index 4da00828d..cf3009c52 100644 --- a/app/protocol/flows/blockrelay/ibd.go +++ b/app/protocol/flows/blockrelay/ibd.go @@ -320,6 +320,40 @@ func (flow *handleRelayInvsFlow) processHeader(consensus externalapi.Consensus, return nil } +func (flow *handleRelayInvsFlow) validatePruningPointFutureHeaderTimestamps() error { + headerSelectedTipHash, err := flow.Domain().StagingConsensus().GetHeadersSelectedTip() + if err != nil { + return err + } + headerSelectedTipHeader, err := flow.Domain().StagingConsensus().GetBlockHeader(headerSelectedTipHash) + if err != nil { + return err + } + headerSelectedTipTimestamp := headerSelectedTipHeader.TimeInMilliseconds() + + currentSelectedTipHash, err := flow.Domain().Consensus().GetHeadersSelectedTip() + if err != nil { + return err + } + currentSelectedTipHeader, err := flow.Domain().Consensus().GetBlockHeader(currentSelectedTipHash) + if err != nil { + return err + } + currentSelectedTipTimestamp := currentSelectedTipHeader.TimeInMilliseconds() + + if headerSelectedTipTimestamp < currentSelectedTipTimestamp { + return protocolerrors.Errorf(false, "the timestamp of the candidate selected "+ + "tip is smaller than the current selected tip") + } + + minTimestampDifferenceInMilliseconds := (10 * time.Minute).Milliseconds() + if headerSelectedTipTimestamp-currentSelectedTipTimestamp < minTimestampDifferenceInMilliseconds { + return protocolerrors.Errorf(false, "difference between the timestamps of "+ + "the current pruning point and the candidate pruning point is too small. Aborting IBD...") + } + return nil +} + func (flow *handleRelayInvsFlow) receiveAndInsertPruningPointUTXOSet( consensus externalapi.Consensus, pruningPointHash *externalapi.DomainHash) (bool, error) { diff --git a/app/protocol/flows/blockrelay/ibd_with_headers_proof.go b/app/protocol/flows/blockrelay/ibd_with_headers_proof.go index cd6844497..605107c23 100644 --- a/app/protocol/flows/blockrelay/ibd_with_headers_proof.go +++ b/app/protocol/flows/blockrelay/ibd_with_headers_proof.go @@ -137,6 +137,11 @@ func (flow *handleRelayInvsFlow) downloadHeadersAndPruningUTXOSet(highHash *exte return protocolerrors.Errorf(true, "the triggering IBD block was not sent") } + err = flow.validatePruningPointFutureHeaderTimestamps() + if err != nil { + return err + } + log.Debugf("Syncing the current pruning point UTXO set") syncedPruningPointUTXOSetSuccessfully, err := flow.syncPruningPointUTXOSet(flow.Domain().StagingConsensus(), proofPruningPoint) if err != nil { diff --git a/domain/miningmanager/mempool/config.go b/domain/miningmanager/mempool/config.go index 1905e4962..dafa12716 100644 --- a/domain/miningmanager/mempool/config.go +++ b/domain/miningmanager/mempool/config.go @@ -54,15 +54,15 @@ type Config struct { // DefaultConfig returns the default mempool configuration func DefaultConfig(dagParams *dagconfig.Params) *Config { - targetBlocksPerSecond := uint64(time.Second / dagParams.TargetTimePerBlock) + targetBlocksPerSecond := time.Second.Seconds() / dagParams.TargetTimePerBlock.Seconds() return &Config{ MaximumTransactionCount: defaultMaximumTransactionCount, - TransactionExpireIntervalDAAScore: defaultTransactionExpireIntervalSeconds / targetBlocksPerSecond, - TransactionExpireScanIntervalDAAScore: defaultTransactionExpireScanIntervalSeconds / targetBlocksPerSecond, + TransactionExpireIntervalDAAScore: uint64(float64(defaultTransactionExpireIntervalSeconds) / targetBlocksPerSecond), + TransactionExpireScanIntervalDAAScore: uint64(float64(defaultTransactionExpireScanIntervalSeconds) / targetBlocksPerSecond), TransactionExpireScanIntervalSeconds: defaultTransactionExpireScanIntervalSeconds, - OrphanExpireIntervalDAAScore: defaultOrphanExpireIntervalSeconds / targetBlocksPerSecond, - OrphanExpireScanIntervalDAAScore: defaultOrphanExpireScanIntervalSeconds / targetBlocksPerSecond, + OrphanExpireIntervalDAAScore: uint64(float64(defaultOrphanExpireIntervalSeconds) / targetBlocksPerSecond), + OrphanExpireScanIntervalDAAScore: uint64(float64(defaultOrphanExpireScanIntervalSeconds) / targetBlocksPerSecond), MaximumOrphanTransactionMass: defaultMaximumOrphanTransactionMass, MaximumOrphanTransactionCount: defaultMaximumOrphanTransactionCount, AcceptNonStandard: dagParams.RelayNonStdTxs, diff --git a/testing/integration/ibd_test.go b/testing/integration/ibd_test.go index 049d788ed..33c47b254 100644 --- a/testing/integration/ibd_test.go +++ b/testing/integration/ibd_test.go @@ -1,6 +1,7 @@ package integration import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "reflect" "sync" @@ -116,7 +117,7 @@ func TestIBDWithPruning(t *testing.T) { } // This should trigger resolving the syncee virtual - syncerTip := mineNextBlock(t, syncer) + syncerTip := mineNextBlockWithMockTimestamps(t, syncer) time.Sleep(time.Second) synceeSelectedTip, err := syncee.rpcClient.GetSelectedTipHash() if err != nil { @@ -132,6 +133,12 @@ func TestIBDWithPruning(t *testing.T) { overrideDAGParams := dagconfig.SimnetParams + // Increase the target time per block so that we could mine + // blocks with timestamps that are spaced far enough apart + // to avoid failing the timestamp threshold validation of + // ibd-with-headers-proof + overrideDAGParams.TargetTimePerBlock = time.Minute + // This is done to make a pruning depth of 6 blocks overrideDAGParams.FinalityDuration = 2 * overrideDAGParams.TargetTimePerBlock overrideDAGParams.K = 0 @@ -178,11 +185,11 @@ func TestIBDWithPruning(t *testing.T) { // block. const synceeOnlyBlocks = 2 for i := 0; i < synceeOnlyBlocks; i++ { - mineNextBlock(t, syncee1) + mineNextBlockWithMockTimestamps(t, syncee1) } for i := 0; i < numBlocks-1; i++ { - mineNextBlock(t, syncer) + mineNextBlockWithMockTimestamps(t, syncer) } testSync(syncer, syncee1) @@ -190,3 +197,38 @@ func TestIBDWithPruning(t *testing.T) { // Test a situation where a node with pruned headers syncs another fresh node. testSync(syncee1, syncee2) } + +var currentMockTimestamp int64 = 0 + +// mineNextBlockWithMockTimestamps mines blocks with large timestamp differences +// between every two blocks. This is done to avoid the timestamp threshold validation +// of ibd-with-headers-proof +func mineNextBlockWithMockTimestamps(t *testing.T, harness *appHarness) *externalapi.DomainBlock { + blockTemplate, err := harness.rpcClient.GetBlockTemplate(harness.miningAddress) + if err != nil { + t.Fatalf("Error getting block template: %+v", err) + } + + block, err := appmessage.RPCBlockToDomainBlock(blockTemplate.Block) + if err != nil { + t.Fatalf("Error converting block: %s", err) + } + + if currentMockTimestamp == 0 { + currentMockTimestamp = block.Header.TimeInMilliseconds() + } else { + currentMockTimestamp += 10_000 + } + mutableHeader := block.Header.ToMutable() + mutableHeader.SetTimeInMilliseconds(currentMockTimestamp) + block.Header = mutableHeader.ToImmutable() + + solveBlock(block) + + _, err = harness.rpcClient.SubmitBlock(block) + if err != nil { + t.Fatalf("Error submitting block: %s", err) + } + + return block +}