1414// See the License for the specific language governing permissions and
1515// limitations under the License.
1616
17+ // The confirmation reconciler manages transaction confirmation queues by:
18+ // - Copying blocks from the canonical chain and the existing confirmation queue
19+ // - Detecting blockchain forks and rebuilding confirmation queues when necessary
20+ // - Filling gaps in confirmation queues by fetching missing blocks
21+ // - Determining when transactions have reached the target confirmation count
1722package ethereum
1823
1924import (
@@ -26,15 +31,17 @@ import (
2631 "github.com/hyperledger/firefly-transaction-manager/pkg/ffcapi"
2732)
2833
34+ // reconcileConfirmationsForTransaction reconciles the confirmation queue for a transaction
2935func (bl * blockListener ) reconcileConfirmationsForTransaction (ctx context.Context , txHash string , existingConfirmations []* ffcapi.MinimalBlockInfo , targetConfirmationCount uint64 ) (* ffcapi.ConfirmationMapUpdateResult , error ) {
30- // Initialize the output context
36+ // Initialize the result with existing confirmations
3137 reconcileResult := & ffcapi.ConfirmationMapUpdateResult {
3238 Confirmations : existingConfirmations ,
3339 NewFork : false ,
3440 Confirmed : false ,
3541 TargetConfirmationCount : targetConfirmationCount ,
3642 }
3743
44+ // Fetch the block containing the transaction
3845 txBlockInfo , err := bl .getBlockInfoContainsTxHash (ctx , txHash )
3946 if err != nil {
4047 log .L (ctx ).Errorf ("Failed to fetch block info using tx hash %s: %v" , txHash , err )
@@ -49,16 +56,17 @@ func (bl *blockListener) reconcileConfirmationsForTransaction(ctx context.Contex
4956 return bl .compareAndUpdateConfirmationQueue (ctx , reconcileResult , txBlockInfo , targetConfirmationCount )
5057}
5158
59+ // compareAndUpdateConfirmationQueue orchestrates the confirmation reconciliation process.
60+ // It builds new confirmations from the canonical chain and fills gaps in the confirmation queue.
5261func (bl * blockListener ) compareAndUpdateConfirmationQueue (ctx context.Context , reconcileResult * ffcapi.ConfirmationMapUpdateResult , txBlockInfo * ffcapi.MinimalBlockInfo , targetConfirmationCount uint64 ) (* ffcapi.ConfirmationMapUpdateResult , error ) {
5362 var err error
54- // Compare the and build the tail part of the confirmation queue using the canonical chain
63+ // Build new confirmations from the canonical chain and get existing confirmations
5564 newConfirmationsWithoutTxBlock , existingConfirmations , returnResult := bl .buildConfirmationQueueUsingCanonicalChain (ctx , reconcileResult , txBlockInfo , targetConfirmationCount )
5665 if returnResult {
5766 return reconcileResult , nil
5867 }
5968
60- // Validate and process existing confirmations
61- // and fill in the gap in the confirmation queue
69+ // Validate existing confirmations and fill gaps in the confirmation queue
6270 var confirmations []* ffcapi.MinimalBlockInfo
6371 var newFork bool
6472 confirmations , newFork , err = bl .checkAndFillInGap (ctx , newConfirmationsWithoutTxBlock , existingConfirmations , txBlockInfo , targetConfirmationCount )
@@ -70,47 +78,52 @@ func (bl *blockListener) compareAndUpdateConfirmationQueue(ctx context.Context,
7078 return reconcileResult , err
7179}
7280
73- // NOTE: this function only build up the confirmation queue uses the in-memory canonical chain
74- // it does not build up the canonical chain
75- // compareAndUpdateConfirmationQueueUsingCanonicalChain compares the existing confirmation queue with the in-memory linked list
76- // this function obtains the read lock on the canonical chain, so it should not make any long-running queries
77-
81+ // buildConfirmationQueueUsingCanonicalChain builds the confirmation queue using the in-memory canonical chain.
82+ // It does not modify the canonical chain itself, only reads from it.
83+ // This function holds a read lock on the canonical chain, so it should not make long-running queries.
7884func (bl * blockListener ) buildConfirmationQueueUsingCanonicalChain (ctx context.Context , reconcileResult * ffcapi.ConfirmationMapUpdateResult , txBlockInfo * ffcapi.MinimalBlockInfo , targetConfirmationCount uint64 ) (newConfirmationsWithoutTxBlock []* ffcapi.MinimalBlockInfo , existingConfirmations []* ffcapi.MinimalBlockInfo , returnResult bool ) {
7985 bl .mux .RLock ()
8086 defer bl .mux .RUnlock ()
8187 txBlockNumber := txBlockInfo .BlockNumber .Uint64 ()
8288 targetBlockNumber := txBlockInfo .BlockNumber .Uint64 () + targetConfirmationCount
8389
90+ // Check if the canonical chain has caught up to the transaction block
8491 chainTail := bl .canonicalChain .Back ().Value .(* ffcapi.MinimalBlockInfo )
8592 if chainTail == nil || chainTail .BlockNumber .Uint64 () < txBlockNumber {
8693 log .L (ctx ).Debugf ("Canonical chain is waiting for the transaction block %d to be indexed" , txBlockNumber )
8794 return nil , nil , true
8895 }
8996
90- // Initialize confirmation map and get existing queue
97+ // Initialize confirmation map and get existing confirmations
9198 existingConfirmations = bl .initializeConfirmationMap (reconcileResult , txBlockInfo )
9299
93- // if the target confirmation count is 0, we should just return the transaction block
100+ // Special case: if targetConfirmationCount is 0, transaction is immediately confirmed
94101 if targetConfirmationCount == 0 {
95102 reconcileResult .Confirmed = true
96103 reconcileResult .Confirmations = []* ffcapi.MinimalBlockInfo {txBlockInfo }
97104 return nil , existingConfirmations , true
98105 }
99106
100- // build the tail part of the queue from the canonical chain
107+ // Build new confirmations from blocks after the transaction block
101108
102109 newConfirmationsWithoutTxBlock = []* ffcapi.MinimalBlockInfo {}
103110 currentBlock := bl .canonicalChain .Front ()
104111 for currentBlock != nil {
105112 currentBlockInfo := currentBlock .Value .(* ffcapi.MinimalBlockInfo )
113+
114+ // If we've reached the target confirmation count, mark as confirmed
106115 if currentBlockInfo .BlockNumber .Uint64 () > targetBlockNumber {
107116 reconcileResult .Confirmed = true
108117 break
109118 }
119+
120+ // Skip blocks at or before the transaction block
110121 if currentBlockInfo .BlockNumber .Uint64 () <= txBlockNumber {
111122 currentBlock = currentBlock .Next ()
112123 continue
113124 }
125+
126+ // Add blocks after the transaction block to confirmations
114127 newConfirmationsWithoutTxBlock = append (newConfirmationsWithoutTxBlock , & ffcapi.MinimalBlockInfo {
115128 BlockHash : currentBlockInfo .BlockHash ,
116129 BlockNumber : fftypes .FFuint64 (currentBlockInfo .BlockNumber .Uint64 ()),
@@ -121,18 +134,22 @@ func (bl *blockListener) buildConfirmationQueueUsingCanonicalChain(ctx context.C
121134 return newConfirmationsWithoutTxBlock , existingConfirmations , false
122135}
123136
137+ // initializeConfirmationMap initializes the confirmation map with the transaction block
138+ // and validates existing confirmations against the current transaction block.
139+ // Returns existing confirmations if valid, or nil if a fork is detected.
124140func (bl * blockListener ) initializeConfirmationMap (reconcileResult * ffcapi.ConfirmationMapUpdateResult , txBlockInfo * ffcapi.MinimalBlockInfo ) []* ffcapi.MinimalBlockInfo {
141+ // If no existing confirmations, initialize with the transaction block
125142 if len (reconcileResult .Confirmations ) == 0 {
126143 reconcileResult .Confirmations = []* ffcapi.MinimalBlockInfo {txBlockInfo }
127144 return nil
128145 }
129146
147+ // Validate existing confirmations against the current transaction block
130148 existingQueue := reconcileResult .Confirmations
131149 if len (existingQueue ) > 0 {
132150 existingTxBlock := existingQueue [0 ]
133151 if ! existingTxBlock .Equal (txBlockInfo ) {
134- // the tx block in the existing queue does not match the new tx block we queried from the chain
135- // rebuild a new confirmation queue with the new tx block
152+ // Transaction block mismatch indicates a fork - rebuild confirmation queue
136153 reconcileResult .NewFork = true
137154 reconcileResult .Confirmations = []* ffcapi.MinimalBlockInfo {txBlockInfo }
138155 return nil
@@ -142,9 +159,13 @@ func (bl *blockListener) initializeConfirmationMap(reconcileResult *ffcapi.Confi
142159 return existingQueue
143160}
144161
162+ // checkAndFillInGap validates existing confirmations, detects forks, and fills gaps
163+ // in the confirmation queue using existing confirmations or fetching missing blocks from the blockchain.
164+ // It ensures the confirmation chain is valid and connected to the transaction block.
145165func (bl * blockListener ) checkAndFillInGap (ctx context.Context , newConfirmationsWithoutTxBlock []* ffcapi.MinimalBlockInfo , existingConfirmations []* ffcapi.MinimalBlockInfo , txBlockInfo * ffcapi.MinimalBlockInfo , targetConfirmationCount uint64 ) ([]* ffcapi.MinimalBlockInfo , bool , error ) {
146166 var hasNewFork bool
147- // check whether there are forks in the newConfirmations
167+
168+ // Detect forks by comparing new confirmations with existing ones
148169 for _ , confirmation := range newConfirmationsWithoutTxBlock {
149170 for _ , existingConfirmation := range existingConfirmations {
150171 if confirmation .BlockNumber .Uint64 () == existingConfirmation .BlockNumber .Uint64 () && ! confirmation .Equal (existingConfirmation ) {
@@ -157,59 +178,75 @@ func (bl *blockListener) checkAndFillInGap(ctx context.Context, newConfirmations
157178 }
158179 }
159180
181+ // Determine the range of blocks to validate and fill gaps
160182 blockNumberToReach := txBlockInfo .BlockNumber .Uint64 () + targetConfirmationCount
161183 var lastValidatedBlock * ffcapi.MinimalBlockInfo
162184 if len (newConfirmationsWithoutTxBlock ) > 0 {
185+ // Start from the block before the first new confirmation
163186 blockNumberToReach = newConfirmationsWithoutTxBlock [0 ].BlockNumber .Uint64 () - 1
164187 lastValidatedBlock = newConfirmationsWithoutTxBlock [0 ]
165188 }
166189
190+ // Fill gaps by validating blocks from target down to transaction block
167191 for i := blockNumberToReach ; i > txBlockInfo .BlockNumber .Uint64 (); i -- {
168- // first use the block info from the confirmation queue if matches are found
169192 fetchedFromExistingQueue := false
170- if lastValidatedBlock != nil {
171193
194+ // First, try to use existing confirmations if they match
195+ if lastValidatedBlock != nil {
172196 for _ , confirmation := range existingConfirmations {
173197 if confirmation .BlockNumber .Uint64 () == i {
174198 if confirmation .IsParentOf (lastValidatedBlock ) {
199+ // Valid existing confirmation - prepend to queue
175200 newConfirmationsWithoutTxBlock = append ([]* ffcapi.MinimalBlockInfo {confirmation }, newConfirmationsWithoutTxBlock ... )
176201 lastValidatedBlock = confirmation
177202 fetchedFromExistingQueue = true
178203 break
179204 }
205+ // Block number matches but parent relationship is invalid - fork detected
180206 hasNewFork = true
181207 }
182208 }
183209 }
210+
184211 if fetchedFromExistingQueue {
185212 continue
186213 }
187- // if no match is found, fetch the block info from the chain
214+
215+ // Fetch block from blockchain if not found in existing confirmations
188216 freshBlockInfo , _ , err := bl .getBlockInfoByNumber (ctx , i , false , "" , "" )
189217 if err != nil {
190218 return nil , hasNewFork , err
191219 }
220+ if freshBlockInfo == nil {
221+ return nil , hasNewFork , i18n .NewError (ctx , msgs .MsgBlockNotAvailable )
222+ }
223+
192224 fetchedBlock := & ffcapi.MinimalBlockInfo {
193225 BlockNumber : fftypes .FFuint64 (freshBlockInfo .Number .BigInt ().Uint64 ()),
194226 BlockHash : freshBlockInfo .Hash .String (),
195227 ParentHash : freshBlockInfo .ParentHash .String (),
196228 }
229+
230+ // Validate parent-child relationship
197231 if lastValidatedBlock != nil && ! fetchedBlock .IsParentOf (lastValidatedBlock ) {
198- // the fetched block is not the parent of the last validated block
199- // chain is not in a stable stable to build the confirmation queue
200232 return nil , hasNewFork , i18n .NewError (ctx , msgs .MsgFailedToBuildConfirmationQueue )
201233 }
234+
235+ // Prepend fetched block to confirmation queue
202236 newConfirmationsWithoutTxBlock = append ([]* ffcapi.MinimalBlockInfo {fetchedBlock }, newConfirmationsWithoutTxBlock ... )
203237 lastValidatedBlock = fetchedBlock
204238 }
205239
206- // we've rebuilt the confirmations queue, now check the front of the queue still connect to the tx block
207- if ! txBlockInfo .IsParentOf (newConfirmationsWithoutTxBlock [0 ]) {
240+ // Final validation: ensure the confirmation chain connects to the transaction block
241+ if len ( newConfirmationsWithoutTxBlock ) > 0 && ! txBlockInfo .IsParentOf (newConfirmationsWithoutTxBlock [0 ]) {
208242 return nil , hasNewFork , i18n .NewError (ctx , msgs .MsgFailedToBuildConfirmationQueue )
209243 }
244+
210245 return append ([]* ffcapi.MinimalBlockInfo {txBlockInfo }, newConfirmationsWithoutTxBlock ... ), hasNewFork , nil
211246}
212247
248+ // ReconcileConfirmationsForTransaction is the public API for reconciling transaction confirmations.
249+ // It delegates to the blockListener's internal reconciliation logic.
213250func (c * ethConnector ) ReconcileConfirmationsForTransaction (ctx context.Context , txHash string , existingConfirmations []* ffcapi.MinimalBlockInfo , targetConfirmationCount uint64 ) (* ffcapi.ConfirmationMapUpdateResult , error ) {
214251 return c .blockListener .reconcileConfirmationsForTransaction (ctx , txHash , existingConfirmations , targetConfirmationCount )
215252}
0 commit comments