Skip to content

Commit f4722e2

Browse files
committed
avoid extra call when the next block is in canonical chain
Signed-off-by: Chengxuan Xing <chengxuan.xing@kaleido.io>
1 parent c49d54e commit f4722e2

File tree

2 files changed

+30
-23
lines changed

2 files changed

+30
-23
lines changed

internal/ethereum/confirmation_reconciler.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,27 @@ func (bl *blockListener) reconcileConfirmationsForTransaction(ctx context.Contex
6161
func (bl *blockListener) compareAndUpdateConfirmationQueue(ctx context.Context, reconcileResult *ffcapi.ConfirmationMapUpdateResult, txBlockInfo *ffcapi.MinimalBlockInfo, targetConfirmationCount uint64) (*ffcapi.ConfirmationMapUpdateResult, error) {
6262
var err error
6363
// Build new confirmations from the canonical chain and get existing confirmations
64-
newConfirmationsWithoutTxBlock, existingConfirmations, returnResult := bl.buildConfirmationQueueUsingCanonicalChain(ctx, reconcileResult, txBlockInfo, targetConfirmationCount)
64+
newConfirmationsWithoutTxBlock, lastValidatedBlock, returnResult := bl.buildConfirmationQueueUsingCanonicalChain(ctx, reconcileResult, txBlockInfo, targetConfirmationCount)
6565
if returnResult {
6666
return reconcileResult, nil
6767
}
6868

69+
// Initialize confirmation map and get existing confirmations
70+
// the init must happen after the canonical chain check to avoid
71+
// confirming blocks that are not yet validated in the canonical chain
72+
existingConfirmations := bl.initializeConfirmationMap(reconcileResult, txBlockInfo)
73+
74+
// Special case: if targetConfirmationCount is 0, transaction is immediately confirmed
75+
if targetConfirmationCount == 0 {
76+
reconcileResult.Confirmed = true
77+
reconcileResult.Confirmations = []*ffcapi.MinimalBlockInfo{txBlockInfo}
78+
return reconcileResult, nil
79+
}
80+
6981
// Validate existing confirmations and fill gaps in the confirmation queue
7082
var confirmations []*ffcapi.MinimalBlockInfo
7183
var newFork bool
72-
confirmations, newFork, err = bl.checkAndFillInGap(ctx, newConfirmationsWithoutTxBlock, existingConfirmations, txBlockInfo, targetConfirmationCount)
84+
confirmations, newFork, err = bl.checkAndFillInGap(ctx, newConfirmationsWithoutTxBlock, existingConfirmations, txBlockInfo, targetConfirmationCount, lastValidatedBlock)
7385
if err != nil {
7486
return reconcileResult, err
7587
}
@@ -81,7 +93,7 @@ func (bl *blockListener) compareAndUpdateConfirmationQueue(ctx context.Context,
8193
// buildConfirmationQueueUsingCanonicalChain builds the confirmation queue using the in-memory canonical chain.
8294
// It does not modify the canonical chain itself, only reads from it.
8395
// This function holds a read lock on the canonical chain, so it should not make long-running queries.
84-
func (bl *blockListener) buildConfirmationQueueUsingCanonicalChain(ctx context.Context, reconcileResult *ffcapi.ConfirmationMapUpdateResult, txBlockInfo *ffcapi.MinimalBlockInfo, targetConfirmationCount uint64) (newConfirmationsWithoutTxBlock []*ffcapi.MinimalBlockInfo, existingConfirmations []*ffcapi.MinimalBlockInfo, returnResult bool) {
96+
func (bl *blockListener) buildConfirmationQueueUsingCanonicalChain(ctx context.Context, reconcileResult *ffcapi.ConfirmationMapUpdateResult, txBlockInfo *ffcapi.MinimalBlockInfo, targetConfirmationCount uint64) (newConfirmationsWithoutTxBlock []*ffcapi.MinimalBlockInfo, lastValidatedBlock *ffcapi.MinimalBlockInfo, returnResult bool) {
8597
bl.mux.RLock()
8698
defer bl.mux.RUnlock()
8799
txBlockNumber := txBlockInfo.BlockNumber.Uint64()
@@ -94,16 +106,6 @@ func (bl *blockListener) buildConfirmationQueueUsingCanonicalChain(ctx context.C
94106
return nil, nil, true
95107
}
96108

97-
// Initialize confirmation map and get existing confirmations
98-
existingConfirmations = bl.initializeConfirmationMap(reconcileResult, txBlockInfo)
99-
100-
// Special case: if targetConfirmationCount is 0, transaction is immediately confirmed
101-
if targetConfirmationCount == 0 {
102-
reconcileResult.Confirmed = true
103-
reconcileResult.Confirmations = []*ffcapi.MinimalBlockInfo{txBlockInfo}
104-
return nil, existingConfirmations, true
105-
}
106-
107109
// Build new confirmations from blocks after the transaction block
108110

109111
newConfirmationsWithoutTxBlock = []*ffcapi.MinimalBlockInfo{}
@@ -114,6 +116,12 @@ func (bl *blockListener) buildConfirmationQueueUsingCanonicalChain(ctx context.C
114116
// If we've reached the target confirmation count, mark as confirmed
115117
if currentBlockInfo.BlockNumber.Uint64() > targetBlockNumber {
116118
reconcileResult.Confirmed = true
119+
// if the canonical chain contains the next block after the target block number,
120+
// and the new confirmations queue is empty,
121+
// we set the last validated block to the next block, so the downstream function can use it validate blocks before it
122+
if len(newConfirmationsWithoutTxBlock) == 0 && currentBlockInfo.BlockNumber.Uint64() == targetBlockNumber+1 {
123+
lastValidatedBlock = currentBlockInfo
124+
}
117125
break
118126
}
119127

@@ -131,7 +139,7 @@ func (bl *blockListener) buildConfirmationQueueUsingCanonicalChain(ctx context.C
131139
})
132140
currentBlock = currentBlock.Next()
133141
}
134-
return newConfirmationsWithoutTxBlock, existingConfirmations, false
142+
return newConfirmationsWithoutTxBlock, lastValidatedBlock, false
135143
}
136144

137145
// initializeConfirmationMap initializes the confirmation map with the transaction block
@@ -162,7 +170,7 @@ func (bl *blockListener) initializeConfirmationMap(reconcileResult *ffcapi.Confi
162170
// checkAndFillInGap validates existing confirmations, detects forks, and fills gaps
163171
// in the confirmation queue using existing confirmations or fetching missing blocks from the blockchain.
164172
// It ensures the confirmation chain is valid and connected to the transaction block.
165-
func (bl *blockListener) checkAndFillInGap(ctx context.Context, newConfirmationsWithoutTxBlock []*ffcapi.MinimalBlockInfo, existingConfirmations []*ffcapi.MinimalBlockInfo, txBlockInfo *ffcapi.MinimalBlockInfo, targetConfirmationCount uint64) ([]*ffcapi.MinimalBlockInfo, bool, error) {
173+
func (bl *blockListener) checkAndFillInGap(ctx context.Context, newConfirmationsWithoutTxBlock []*ffcapi.MinimalBlockInfo, existingConfirmations []*ffcapi.MinimalBlockInfo, txBlockInfo *ffcapi.MinimalBlockInfo, targetConfirmationCount uint64, lastValidatedBlock *ffcapi.MinimalBlockInfo) ([]*ffcapi.MinimalBlockInfo, bool, error) {
166174
var hasNewFork bool
167175

168176
// Detect forks by comparing new confirmations with existing ones
@@ -180,7 +188,6 @@ func (bl *blockListener) checkAndFillInGap(ctx context.Context, newConfirmations
180188

181189
// Determine the range of blocks to validate and fill gaps
182190
blockNumberToReach := txBlockInfo.BlockNumber.Uint64() + targetConfirmationCount
183-
var lastValidatedBlock *ffcapi.MinimalBlockInfo
184191
if len(newConfirmationsWithoutTxBlock) > 0 {
185192
// Start from the block before the first new confirmation
186193
blockNumberToReach = newConfirmationsWithoutTxBlock[0].BlockNumber.Uint64() - 1

internal/ethereum/confirmation_reconciler_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ func TestCompareAndUpdateConfirmationQueue_ExistingTxBockInfoIsWrong(t *testing.
866866

867867
func TestCompareAndUpdateConfirmationQueue_AlreadyConfirmable(t *testing.T) {
868868
// Setup
869-
bl, done := newBlockListenerWithTestChain(t, 100, 5, 103, 150, []uint64{102})
869+
bl, done := newBlockListenerWithTestChain(t, 100, 5, 103, 150, []uint64{})
870870
defer done()
871871
ctx := context.Background()
872872
// Create confirmations that already meet the target
@@ -992,7 +992,7 @@ func TestCompareAndUpdateConfirmationQueue_AlreadyConfirmableButAllExistingConfi
992992
// and it connects to the canonical chain to validate they are still valid
993993
existingQueue := []*ffcapi.MinimalBlockInfo{
994994
{BlockHash: generateTestHash(100), BlockNumber: fftypes.FFuint64(100), ParentHash: generateTestHash(99)},
995-
// gap of 101 is allowed, and is the confirmation required for the transaction with target confirmation count of 1
995+
// 101 will be fetched from the JSON-RPC endpoint to fill the gap
996996
{BlockHash: generateTestHash(102), BlockNumber: fftypes.FFuint64(102), ParentHash: generateTestHash(101)},
997997
}
998998
occ := &ffcapi.ConfirmationMapUpdateResult{
@@ -1165,7 +1165,7 @@ func TestCompareAndUpdateConfirmationQueue_ExistingConfirmationsWithGap(t *testi
11651165
// Create confirmations with a gap (missing block 102)
11661166
existingQueue := []*ffcapi.MinimalBlockInfo{
11671167
{BlockHash: generateTestHash(100), BlockNumber: fftypes.FFuint64(100), ParentHash: generateTestHash(99)},
1168-
// no block 101, which is the first block of the canonical chain
1168+
// no block 101, which is the first block of the canonical chain, so no fetch to JSON-RPC endpoint is needed
11691169
{BlockHash: generateTestHash(102), BlockNumber: fftypes.FFuint64(102), ParentHash: generateTestHash(101)},
11701170
{BlockHash: generateTestHash(103), BlockNumber: fftypes.FFuint64(103), ParentHash: generateTestHash(102)},
11711171
}
@@ -1329,7 +1329,7 @@ func TestCheckAndFillInGap_GetBlockInfoError(t *testing.T) {
13291329
}), false).Return(&rpcbackend.RPCError{Message: "pop"})
13301330

13311331
// Execute
1332-
result, hasNewFork, err := c.blockListener.checkAndFillInGap(ctx, newConfirmationsWithoutTxBlock, existingConfirmations, txBlockInfo, targetConfirmationCount)
1332+
result, hasNewFork, err := c.blockListener.checkAndFillInGap(ctx, newConfirmationsWithoutTxBlock, existingConfirmations, txBlockInfo, targetConfirmationCount, nil)
13331333

13341334
// Assertions
13351335
assert.Error(t, err)
@@ -1361,7 +1361,7 @@ func TestCheckAndFillInGap_BlockNotAvailable(t *testing.T) {
13611361
})
13621362

13631363
// Execute
1364-
result, hasNewFork, err := c.blockListener.checkAndFillInGap(ctx, newConfirmationsWithoutTxBlock, existingConfirmations, txBlockInfo, targetConfirmationCount)
1364+
result, hasNewFork, err := c.blockListener.checkAndFillInGap(ctx, newConfirmationsWithoutTxBlock, existingConfirmations, txBlockInfo, targetConfirmationCount, nil)
13651365

13661366
// Assertions
13671367
assert.Error(t, err)
@@ -1405,7 +1405,7 @@ func TestCheckAndFillInGap_InvalidBlockParentRelationship(t *testing.T) {
14051405
})
14061406

14071407
// Execute
1408-
result, hasNewFork, err := c.blockListener.checkAndFillInGap(ctx, newConfirmationsWithoutTxBlock, existingConfirmations, txBlockInfo, targetConfirmationCount)
1408+
result, hasNewFork, err := c.blockListener.checkAndFillInGap(ctx, newConfirmationsWithoutTxBlock, existingConfirmations, txBlockInfo, targetConfirmationCount, nil)
14091409

14101410
// Assertions - should fail because block 102 has wrong parent hash and doesn't connect to block 101
14111411
assert.Error(t, err)
@@ -1438,7 +1438,7 @@ func TestCheckAndFillInGap_TxBlockNotParentOfFirstConfirmation(t *testing.T) {
14381438
existingConfirmations := []*ffcapi.MinimalBlockInfo{}
14391439

14401440
// Execute
1441-
result, hasNewFork, err := c.blockListener.checkAndFillInGap(ctx, newConfirmationsWithoutTxBlock, existingConfirmations, txBlockInfo, targetConfirmationCount)
1441+
result, hasNewFork, err := c.blockListener.checkAndFillInGap(ctx, newConfirmationsWithoutTxBlock, existingConfirmations, txBlockInfo, targetConfirmationCount, nil)
14421442

14431443
// Assertions
14441444
assert.Error(t, err)

0 commit comments

Comments
 (0)