Skip to content

Conversation

Chengxuan
Copy link
Contributor

@Chengxuan Chengxuan commented Sep 10, 2025

Requires hyperledger/firefly-transaction-manager#148

This PR introduced a new method, ReconcileConfirmationsForTransaction, which can be used to directly calculate the confirmation map of a given transaction.

The diagram below shows the core logic of the function:
image

Details of the confirmation Map struct can be found in this PR: hyperledger/firefly-transaction-manager#148

Reconciliation logic for an existing confirmations that get passed in

  • If the transaction is in a block hash that hasn't been recorded, that new block hash will be added to the map and a new confirmation queue will be built and stored under that block hash (map key)
  • If the transaction is in an existing block hash that's been tracked
    • If any of the existing confirmation block numbers are connectable to any of the block numbers in the current in-memory canonical chain
      • if all of them are chainable (all on the same fork or partially on the same fork), the confirmations on the same fork will be carried over into the new confirmation map, the newFork flag will be set to true in the response accordingly
    • otherwise they are discarded

Confirmation queue for different scenarios:

  • In all cases, the first item in the confirmation queue is the tx block (the block that contains the tx).
  • In the following scenario, a tx confirmation queue will have a full set of MinimalBlockInfo as the confirmation history
    • all confirmation blocks are in the current in-memory canonical chain
    • existing confirmation queue (provided by the consumer) shares a fork with the current in-memory canonical chain, all confirmation blocks, and:
      • all confirmation blocks are in the existing queue
      • or all confirmation blocks are in the existing queue + the in-memory canonical chain
    • In the following scenario, a tx confirmation queue will have partial confirmation blocks:
      • no existing confirmations provided, some confirmation blocks are ahead of the first item in the in-memory canonical chain.
      • existing confirmation queue shares a fork with current in-memory canonical chain, but it only has partial confirmation blocks in the existing confirmation queue.
    • In the scenario of the target confirmation block number of a transaction is ahead of the in-memory canonical chain or the combined existing confirmation + in-memory canonical chain blocks, the confirmation queue will only have 2 items: the tx block and the head block of the in-memory canonical chain.

Signed-off-by: Chengxuan Xing <chengxuan.xing@kaleido.io>
Signed-off-by: Chengxuan Xing <chengxuan.xing@kaleido.io>
Signed-off-by: Chengxuan Xing <chengxuan.xing@kaleido.io>
Signed-off-by: Chengxuan Xing <chengxuan.xing@kaleido.io>
Chengxuan and others added 4 commits September 11, 2025 19:21
Co-authored-by: alexey semenyuk <alexsemenyuk88@gmail.com>
Signed-off-by: Chengxuan Xing <chengxuan.xing@kaleido.io>
Signed-off-by: Chengxuan Xing <chengxuan.xing@kaleido.io>
Signed-off-by: Chengxuan Xing <chengxuan.xing@kaleido.io>
Signed-off-by: Chengxuan Xing <chengxuan.xing@kaleido.io>
Copy link
Contributor

@EnriqueL8 EnriqueL8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks really good - a few questions and suggestions but thanks for all the comments in the flow

// Note: should consider have an in-memory map of transaction hash to block for faster lookup
// The extra memory usage of the map should be outweighed by the speed improvement of lookup
// But I saw we have a ffcapi.MinimalBlockInfo struct that intentionally removes the tx hashes
// so need to figure out the reason first
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you going to do this figuring out as part of the PR? I did wonder why the TX hash -> block was not a thing

}

// Compare the existing confirmation queue with the in-memory linked list
bl.compareAndUpdateConfirmationQueue(ctx, occ, txBlockInfo, targetConfirmationCount)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no error ?


chainHead := bl.canonicalChain.Front().Value.(*ffcapi.MinimalBlockInfo)
chainTail := bl.canonicalChain.Back().Value.(*ffcapi.MinimalBlockInfo)
if chainHead == nil || chainTail == nil || chainTail.BlockNumber.Uint64() < txBlockNumber {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we check that the canonical chain's tail (oldest block) is smaller than the TX block number - that could happen and still be correct to carry on no? or is the tail the newest block?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or did you mean to check the chainHead?

}
}

func (bl *blockListener) initializeConfirmationMap(occ *ffcapi.ConfirmationMapUpdateResult, txBlockInfo *ffcapi.MinimalBlockInfo) []*ffcapi.MinimalBlockInfo {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this occ naming format? maybe confMapResult to keep with txBlockInfo

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah it's output context, but it's not a like a OutputContext object that is why the confusion

return existingQueue
}

func (bl *blockListener) processExistingConfirmations(ctx context.Context, occ *ffcapi.ConfirmationMapUpdateResult, txBlockInfo *ffcapi.MinimalBlockInfo, existingQueue []*ffcapi.MinimalBlockInfo, chainHead *ffcapi.MinimalBlockInfo, targetConfirmationCount uint64) ([]*ffcapi.MinimalBlockInfo, *list.Element) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a nit so up to you but maybe to reduce number of params as you have done in the past having a context object with all the things we need

}

// If we've trimmed off all the existing confirmations, we need to add the canonical chain head
// to tell use the head block we used to confirmed the transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// to tell use the head block we used to confirmed the transaction
// to tell us the head block we used to confirm the transaction

confirmationBlockNumber := txBlockNumber + targetConfirmationCount
if lastBlockInNewQueue.BlockNumber.Uint64() >= confirmationBlockNumber {
chainHead := bl.canonicalChain.Front().Value.(*ffcapi.MinimalBlockInfo)
// we've got a confirmable so whether the rest of the chain has forked is not longer relevant
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// we've got a confirmable so whether the rest of the chain has forked is not longer relevant
// we've got a confirmation so whether the rest of the chain has forked is no longer relevant

)
}

func (bl *blockListener) validateExistingConfirmations(ctx context.Context, occ *ffcapi.ConfirmationMapUpdateResult, newQueue []*ffcapi.MinimalBlockInfo, existingConfirmations []*ffcapi.MinimalBlockInfo, currentBlock *list.Element, chainHead *ffcapi.MinimalBlockInfo, txBlockInfo *ffcapi.MinimalBlockInfo, targetConfirmationCount uint64) ([]*ffcapi.MinimalBlockInfo, *list.Element) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function was quite difficult to follow FYI

existingQueue := bl.initializeConfirmationMap(occ, txBlockInfo)

// Validate and process existing confirmations
newQueue, currentBlock := bl.processExistingConfirmations(ctx, occ, txBlockInfo, existingQueue, chainHead, targetConfirmationCount)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this name of currentBlock is quite confusing... I get the name cause it can be the front of the chain, the block at which the confirmation reached the limit...

Signed-off-by: Chengxuan Xing <chengxuan.xing@kaleido.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants