Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions services/blockvalidation/Server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1161,7 +1161,7 @@ func (u *Server) ValidateBlock(ctx context.Context, request *blockvalidation_api
defer deferFn()

// Wait for block assembly to be ready before processing the block
if err = blockassemblyutil.WaitForBlockAssemblyReady(ctx, u.logger, u.blockAssemblyClient, block.Height); err != nil {
if err = blockassemblyutil.WaitForBlockAssemblyReady(ctx, u.logger, u.blockAssemblyClient, block.Height, u.settings.BlockValidation.MaxBlocksBehindBlockAssembly); err != nil {
// block-assembly is still behind, so we cannot process this block
return nil, errors.WrapGRPC(err)
}
Expand Down Expand Up @@ -1275,7 +1275,7 @@ func (u *Server) processBlockFound(ctx context.Context, hash *chainhash.Hash, ba
}

// Wait for block assembly to be ready before processing the block
if err = blockassemblyutil.WaitForBlockAssemblyReady(ctx, u.logger, u.blockAssemblyClient, block.Height); err != nil {
if err = blockassemblyutil.WaitForBlockAssemblyReady(ctx, u.logger, u.blockAssemblyClient, block.Height, u.settings.BlockValidation.MaxBlocksBehindBlockAssembly); err != nil {
// block-assembly is still behind, so we cannot process this block
return err
}
Expand Down
2 changes: 1 addition & 1 deletion services/blockvalidation/catchup.go
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ func (u *Server) validateBlocksOnChannel(validateBlocksChan chan *model.Block, g
}

// Wait for block assembly to be ready if needed
if err := blockassemblyutil.WaitForBlockAssemblyReady(gCtx, u.logger, u.blockAssemblyClient, block.Height); err != nil {
if err := blockassemblyutil.WaitForBlockAssemblyReady(gCtx, u.logger, u.blockAssemblyClient, block.Height, u.settings.BlockValidation.MaxBlocksBehindBlockAssembly); err != nil {
return errors.NewProcessingError("[catchup:validateBlocksOnChannel][%s] failed to wait for block assembly for block %s: %v", blockUpTo.Hash().String(), block.Hash().String(), err)
}

Expand Down
2 changes: 1 addition & 1 deletion services/legacy/netsync/handle_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (sm *SyncManager) HandleBlockDirect(ctx context.Context, peer *peer.Peer, b
}()

// Wait for block assembly to be ready
if err = blockassemblyutil.WaitForBlockAssemblyReady(ctx, sm.logger, sm.blockAssembly, blockHeight); err != nil {
if err = blockassemblyutil.WaitForBlockAssemblyReady(ctx, sm.logger, sm.blockAssembly, blockHeight, sm.settings.BlockValidation.MaxBlocksBehindBlockAssembly); err != nil {
// block-assembly is still behind, so we cannot process this block
return err
}
Expand Down
4 changes: 2 additions & 2 deletions services/legacy/netsync/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -1129,8 +1129,6 @@ func (sm *SyncManager) handleBlockMsg(bmsg *blockQueueMsg) error {
// TODO TEMPORARY: we should not panic here, but return the error
panic(err)
}
} else {
sm.logger.Infof("accepted block %v", bmsg.blockHash)
}

// Meta-data about the new block this peer is reporting. We use this
Expand Down Expand Up @@ -1177,6 +1175,8 @@ func (sm *SyncManager) handleBlockMsg(bmsg *blockQueueMsg) error {
}
}

sm.logger.Infof("accepted block %v at height %d", bmsg.blockHash, heightUpdate)

// Clear the rejected transactions.
sm.rejectedTxns.Clear()

Expand Down
1 change: 1 addition & 0 deletions settings/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ type BlockValidationSettings struct {
CheckSubtreeFromBlockRetryBackoffDuration time.Duration
SecretMiningThreshold uint32
PreviousBlockHeaderCount uint64
MaxBlocksBehindBlockAssembly int
// Catchup configuration
CatchupMaxRetries int // Maximum number of retries for catchup operations
CatchupIterationTimeout int // Timeout in seconds for each catchup iteration
Expand Down
1 change: 1 addition & 0 deletions settings/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ func NewSettings(alternativeContext ...string) *Settings {
CheckSubtreeFromBlockRetryBackoffDuration: getDuration("blockvalidation_check_subtree_from_block_retry_backoff_duration", 30*time.Second),
SecretMiningThreshold: getUint32("blockvalidation_secret_mining_threshold", uint32(params.CoinbaseMaturity-1), alternativeContext...), // golint:nolint
PreviousBlockHeaderCount: getUint64("blockvalidation_previous_block_header_count", 100, alternativeContext...),
MaxBlocksBehindBlockAssembly: getInt("blockvalidation_maxBlocksBehindBlockAssembly", 20, alternativeContext...),
// Catchup configuration
CatchupMaxRetries: getInt("blockvalidation_catchup_max_retries", 3, alternativeContext...),
CatchupIterationTimeout: getInt("blockvalidation_catchup_iteration_timeout", 30, alternativeContext...),
Expand Down
18 changes: 8 additions & 10 deletions util/blockassemblyutil/blockassembly_wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (
// a block at the given height. This ensures that all necessary data (such as coinbase
// transactions) has been processed before allowing block validation to proceed.
//
// The function implements a retry mechanism with exponential backoff, checking if the
// block assembly service is at most 1 block behind the target height. This prevents the
// The function implements a retry mechanism with linear backoff, checking if the
// block assembly service is not too far behind the target height. This prevents the
// blockchain state from running too far ahead of block assembly, which would cause
// coinbase maturity checks to fail incorrectly in the UTXO store.
//
Expand All @@ -26,6 +26,7 @@ import (
// - logger: Logger for recording operations
// - blockAssemblyClient: Client interface to the block assembly service
// - blockHeight: The height of the block to be processed
// - maxBlocksBehind: Maximum number of blocks block assembly can be behind
//
// Returns:
// - error: nil if block assembly is ready, error if timeout or other failure
Expand All @@ -34,33 +35,30 @@ func WaitForBlockAssemblyReady(
logger ulogger.Logger,
blockAssemblyClient blockassembly.ClientI,
blockHeight uint32,
maxBlocksBehind int,
) error {
// Skip if block assembly client is not available (e.g., in tests)
if blockAssemblyClient == nil {
return nil
}

// Block assembly must be at most 1 block behind to prevent UTXO store
// coinbase maturity checks from failing with incorrect "current height"
const maxBlocksBehind uint32 = 1

// Check that block assembly is not more than 1 block behind
// Check that block assembly is not more than maxBlocksBehind blocks behind
// This is to make sure all the coinbases have been processed in the block assembly
_, err := retry.Retry(ctx, logger, func() (uint32, error) {
blockAssemblyStatus, err := blockAssemblyClient.GetBlockAssemblyState(ctx)
if err != nil {
return 0, errors.NewProcessingError("failed to get block assembly state", err)
}

if blockAssemblyStatus.CurrentHeight+maxBlocksBehind < blockHeight {
if blockAssemblyStatus.CurrentHeight+uint32(maxBlocksBehind) < blockHeight {
return 0, errors.NewProcessingError("block assembly is behind, block height %d, block assembly height %d", blockHeight, blockAssemblyStatus.CurrentHeight)
}

return blockAssemblyStatus.CurrentHeight, nil
},
retry.WithRetryCount(100),
retry.WithBackoffDurationType(time.Millisecond),
retry.WithBackoffMultiplier(10),
retry.WithBackoffDurationType(20*time.Millisecond),
retry.WithBackoffMultiplier(4),
retry.WithMessage(fmt.Sprintf("[WaitForBlockAssemblyReady] block assembly block height %d is behind, waiting", blockHeight)),
)

Expand Down
20 changes: 11 additions & 9 deletions util/blockassemblyutil/blockassembly_wait_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@ func TestWaitForBlockAssemblyReady(t *testing.T) {
blockHeight: 100,
blockHash: "test-hash",
setupMock: func(m *blockassembly.Mock) {
// First call - behind
// First call - behind (CurrentHeight: 88 + maxBlocksBehind: 10 = 98, which is < 100)
m.On("GetBlockAssemblyState", mock.Anything).Return(
&blockassembly_api.StateMessage{CurrentHeight: 98},
&blockassembly_api.StateMessage{CurrentHeight: 88},
nil,
).Once()
// Second call - still behind
m.On("GetBlockAssemblyState", mock.Anything).Return(
&blockassembly_api.StateMessage{CurrentHeight: 98},
&blockassembly_api.StateMessage{CurrentHeight: 88},
nil,
).Once()
// Third call - caught up
// Third call - caught up (88 + 10 = 98, still behind, so need 90+)
m.On("GetBlockAssemblyState", mock.Anything).Return(
&blockassembly_api.StateMessage{CurrentHeight: 99},
&blockassembly_api.StateMessage{CurrentHeight: 90},
nil,
).Once()
},
Expand All @@ -75,9 +75,9 @@ func TestWaitForBlockAssemblyReady(t *testing.T) {
blockHeight: 100,
blockHash: "test-hash",
setupMock: func(m *blockassembly.Mock) {
// Always return behind height
// Always return behind height (CurrentHeight: 88 + maxBlocksBehind: 10 = 98, which is < 100)
m.On("GetBlockAssemblyState", mock.Anything).Return(
&blockassembly_api.StateMessage{CurrentHeight: 98},
&blockassembly_api.StateMessage{CurrentHeight: 88},
nil,
)
},
Expand Down Expand Up @@ -128,6 +128,7 @@ func TestWaitForBlockAssemblyReady(t *testing.T) {
logger,
mockClient,
tt.blockHeight,
10, // maxBlocksBehind
)

if tt.expectedError {
Expand Down Expand Up @@ -155,9 +156,9 @@ func TestWaitForBlockAssemblyReady(t *testing.T) {
func TestWaitForBlockAssemblyReadyContextCancellation(t *testing.T) {
mockClient := &blockassembly.Mock{}

// Make block assembly always behind
// Make block assembly always behind (CurrentHeight: 88 + maxBlocksBehind: 10 = 98, which is < 100)
mockClient.On("GetBlockAssemblyState", mock.Anything).Return(
&blockassembly_api.StateMessage{CurrentHeight: 98},
&blockassembly_api.StateMessage{CurrentHeight: 88},
nil,
)

Expand All @@ -171,6 +172,7 @@ func TestWaitForBlockAssemblyReadyContextCancellation(t *testing.T) {
logger,
mockClient,
100,
10, // maxBlocksBehind
)

assert.Error(t, err)
Expand Down
8 changes: 5 additions & 3 deletions util/retry/retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ func Retry[T any](ctx context.Context, logger ulogger.Logger, f func() (T, error
waitTime = time.Duration(backoff) * setOptions.BackoffDurationType
}

// Log the retry message with wait time
logger.Warnf(setOptions.Message+" (attempt %d): %v, trying again in %.1f seconds",
i+1, err, waitTime.Seconds())
// Log the retry message with wait time (skip first 5 attempts to reduce noise)
if i >= 5 {
logger.Warnf(setOptions.Message+" (attempt %d): %v, trying again in %.1f seconds",
i+1, err, waitTime.Seconds())
}

// Wait before retrying
if setOptions.ExponentialBackoff {
Expand Down
Loading