From f4912adbd28b8895856e1a34bbad1530d9df777d Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 23 Feb 2024 12:42:42 -0500 Subject: [PATCH 01/84] add count transactions by state --- common/txmgr/address_state.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index f6fd8d05433..c4b94fb6d98 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -122,6 +122,26 @@ func NewAddressState[ // CountTransactionsByState returns the number of transactions that are in the given state func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountTransactionsByState(txState txmgrtypes.TxState) int { + as.RLock() + defer as.RUnlock() + + switch txState { + case TxUnstarted: + return as.unstarted.Len() + case TxInProgress: + if as.inprogress != nil { + return 1 + } + case TxUnconfirmed: + return len(as.unconfirmed) + case TxConfirmedMissingReceipt: + return len(as.confirmedMissingReceipt) + case TxConfirmed: + return len(as.confirmed) + case TxFatalError: + return len(as.fatalErrored) + } + return 0 } From cc453d9dfecf4846895a96ea6ceb3729ea10d434 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 23 Feb 2024 12:51:46 -0500 Subject: [PATCH 02/84] add FindTxWithIdempotencyKey --- common/txmgr/address_state.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index c4b94fb6d98..0e7afc0bd6c 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -120,7 +120,7 @@ func NewAddressState[ return &as, nil } -// CountTransactionsByState returns the number of transactions that are in the given state +// CountTransactionsByState returns the number of transactions that are in the given state. func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountTransactionsByState(txState txmgrtypes.TxState) int { as.RLock() defer as.RUnlock() @@ -145,9 +145,13 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountT return 0 } -// FindTxWithIdempotencyKey returns the transaction with the given idempotency key. If no transaction is found, nil is returned. +// FindTxWithIdempotencyKey returns the transaction with the given idempotency key. +// If no transaction is found, nil is returned. func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxWithIdempotencyKey(key string) *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] { - return nil + as.RLock() + defer as.RUnlock() + + return as.idempotencyKeyToTx[key] } // ApplyToTxsByState calls the given function for each transaction in the given states. From a98bfea6dfc9b89b166aa81dbd49ff0a5a92310d Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 23 Feb 2024 12:53:51 -0500 Subject: [PATCH 03/84] implement ApplyToTxsByState --- common/txmgr/address_state.go | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index 0e7afc0bd6c..9323cfa658c 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -163,6 +163,31 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ApplyT fn func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]), txIDs ...int64, ) { + as.Lock() + defer as.Unlock() + + // if txStates is empty then apply the filter to only the as.allTransactions map + if len(txStates) == 0 { + as.applyToTxs(as.allTransactions, fn, txIDs...) + return + } + + for _, txState := range txStates { + switch txState { + case TxInProgress: + if as.inprogress != nil { + fn(as.inprogress) + } + case TxUnconfirmed: + as.applyToTxs(as.unconfirmed, fn, txIDs...) + case TxConfirmedMissingReceipt: + as.applyToTxs(as.confirmedMissingReceipt, fn, txIDs...) + case TxConfirmed: + as.applyToTxs(as.confirmed, fn, txIDs...) + case TxFatalError: + as.applyToTxs(as.fatalErrored, fn, txIDs...) + } + } } // FetchTxAttempts returns all attempts for the given transactions that match the given filters. @@ -279,3 +304,25 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) MoveIn func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) MoveConfirmedToUnconfirmed(attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { return nil } + +func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) applyToTxs( + txIDsToTx map[int64]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], + fn func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]), + txIDs ...int64, +) { + // if txIDs is not empty then only apply the filter to those transactions + if len(txIDs) > 0 { + for _, txID := range txIDs { + tx := txIDsToTx[txID] + if tx != nil { + fn(tx) + } + } + return + } + + // if txIDs is empty then apply the filter to all transactions + for _, tx := range txIDsToTx { + fn(tx) + } +} From 6fbebff65f9c3d96de74960e311352fcf46f798b Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 23 Feb 2024 12:55:04 -0500 Subject: [PATCH 04/84] implement FetchTxAttempts --- common/txmgr/address_state.go | 71 ++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index 9323cfa658c..cb7d9f3f5a8 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -201,7 +201,37 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FetchT txAttemptFilter func(*txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, txIDs ...int64, ) []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] { - return nil + as.RLock() + defer as.RUnlock() + + // if txStates is empty then apply the filter to only the as.allTransactions map + if len(txStates) == 0 { + return as.fetchTxAttempts(as.allTransactions, txFilter, txAttemptFilter, txIDs...) + } + + var txAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + for _, txState := range txStates { + switch txState { + case TxInProgress: + if as.inprogress != nil && txFilter(as.inprogress) { + for _, txAttempt := range as.inprogress.TxAttempts { + if txAttemptFilter(&txAttempt) { + txAttempts = append(txAttempts, txAttempt) + } + } + } + case TxUnconfirmed: + txAttempts = append(txAttempts, as.fetchTxAttempts(as.unconfirmed, txFilter, txAttemptFilter, txIDs...)...) + case TxConfirmedMissingReceipt: + txAttempts = append(txAttempts, as.fetchTxAttempts(as.confirmedMissingReceipt, txFilter, txAttemptFilter, txIDs...)...) + case TxConfirmed: + txAttempts = append(txAttempts, as.fetchTxAttempts(as.confirmed, txFilter, txAttemptFilter, txIDs...)...) + case TxFatalError: + txAttempts = append(txAttempts, as.fetchTxAttempts(as.fatalErrored, txFilter, txAttemptFilter, txIDs...)...) + } + } + + return txAttempts } // FetchTxs returns all transactions that match the given filters. @@ -326,3 +356,42 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) applyT fn(tx) } } + +func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) fetchTxAttempts( + txIDsToTx map[int64]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], + txFilter func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, + txAttemptFilter func(*txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, + txIDs ...int64, +) []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] { + as.RLock() + defer as.RUnlock() + + var txAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + // if txIDs is not empty then only apply the filter to those transactions + if len(txIDs) > 0 { + for _, txID := range txIDs { + tx := txIDsToTx[txID] + if tx != nil && txFilter(tx) { + for _, txAttempt := range tx.TxAttempts { + if txAttemptFilter(&txAttempt) { + txAttempts = append(txAttempts, txAttempt) + } + } + } + } + return txAttempts + } + + // if txIDs is empty then apply the filter to all transactions + for _, tx := range txIDsToTx { + if txFilter(tx) { + for _, txAttempt := range tx.TxAttempts { + if txAttemptFilter(&txAttempt) { + txAttempts = append(txAttempts, txAttempt) + } + } + } + } + + return txAttempts +} From 0e87297216118733d571d5223ed03138925d0057 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 23 Feb 2024 12:56:12 -0500 Subject: [PATCH 05/84] implement FetchTxs --- common/txmgr/address_state.go | 58 ++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index cb7d9f3f5a8..2e9e0d5018e 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -243,7 +243,33 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FetchT filter func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, txIDs ...int64, ) []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] { - return nil + as.RLock() + defer as.RUnlock() + + // if txStates is empty then apply the filter to only the as.allTransactions map + if len(txStates) == 0 { + return as.fetchTxs(as.allTransactions, filter, txIDs...) + } + + var txs []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + for _, txState := range txStates { + switch txState { + case TxInProgress: + if as.inprogress != nil && filter(as.inprogress) { + txs = append(txs, *as.inprogress) + } + case TxUnconfirmed: + txs = append(txs, as.fetchTxs(as.unconfirmed, filter, txIDs...)...) + case TxConfirmedMissingReceipt: + txs = append(txs, as.fetchTxs(as.confirmedMissingReceipt, filter, txIDs...)...) + case TxConfirmed: + txs = append(txs, as.fetchTxs(as.confirmed, filter, txIDs...)...) + case TxFatalError: + txs = append(txs, as.fetchTxs(as.fatalErrored, filter, txIDs...)...) + } + } + + return txs } // PruneUnstartedTxQueue removes the transactions with the given IDs from the unstarted transaction queue. @@ -395,3 +421,33 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) fetchT return txAttempts } + +func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) fetchTxs( + txIDsToTx map[int64]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], + filter func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, + txIDs ...int64, +) []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] { + as.RLock() + defer as.RUnlock() + + var txs []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + // if txIDs is not empty then only apply the filter to those transactions + if len(txIDs) > 0 { + for _, txID := range txIDs { + tx := txIDsToTx[txID] + if tx != nil && filter(tx) { + txs = append(txs, *tx) + } + } + return txs + } + + // if txIDs is empty then apply the filter to all transactions + for _, tx := range txIDsToTx { + if filter(tx) { + txs = append(txs, *tx) + } + } + + return txs +} From 2a33a73272e79f7a2fc7d9938731854c92a11d8a Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 23 Feb 2024 13:03:31 -0500 Subject: [PATCH 06/84] cleanup --- common/txmgr/address_state.go | 41 +++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index 2e9e0d5018e..53b94bcab0c 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -190,12 +190,12 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ApplyT } } -// FetchTxAttempts returns all attempts for the given transactions that match the given filters. +// FindTxAttempts returns all attempts for the given transactions that match the given filters. // If txIDs are provided, only the transactions with those IDs are considered. // If no txIDs are provided, all transactions are considered. // If no txStates are provided, all transactions are considered. // The txFilter is applied to the transactions and the txAttemptFilter is applied to the attempts. -func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FetchTxAttempts( +func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxAttempts( txStates []txmgrtypes.TxState, txFilter func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, txAttemptFilter func(*txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, @@ -206,7 +206,7 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FetchT // if txStates is empty then apply the filter to only the as.allTransactions map if len(txStates) == 0 { - return as.fetchTxAttempts(as.allTransactions, txFilter, txAttemptFilter, txIDs...) + return as.findTxAttempts(as.allTransactions, txFilter, txAttemptFilter, txIDs...) } var txAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] @@ -214,31 +214,32 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FetchT switch txState { case TxInProgress: if as.inprogress != nil && txFilter(as.inprogress) { - for _, txAttempt := range as.inprogress.TxAttempts { + for i := 0; i < len(as.inprogress.TxAttempts); i++ { + txAttempt := as.inprogress.TxAttempts[i] if txAttemptFilter(&txAttempt) { txAttempts = append(txAttempts, txAttempt) } } } case TxUnconfirmed: - txAttempts = append(txAttempts, as.fetchTxAttempts(as.unconfirmed, txFilter, txAttemptFilter, txIDs...)...) + txAttempts = append(txAttempts, as.findTxAttempts(as.unconfirmed, txFilter, txAttemptFilter, txIDs...)...) case TxConfirmedMissingReceipt: - txAttempts = append(txAttempts, as.fetchTxAttempts(as.confirmedMissingReceipt, txFilter, txAttemptFilter, txIDs...)...) + txAttempts = append(txAttempts, as.findTxAttempts(as.confirmedMissingReceipt, txFilter, txAttemptFilter, txIDs...)...) case TxConfirmed: - txAttempts = append(txAttempts, as.fetchTxAttempts(as.confirmed, txFilter, txAttemptFilter, txIDs...)...) + txAttempts = append(txAttempts, as.findTxAttempts(as.confirmed, txFilter, txAttemptFilter, txIDs...)...) case TxFatalError: - txAttempts = append(txAttempts, as.fetchTxAttempts(as.fatalErrored, txFilter, txAttemptFilter, txIDs...)...) + txAttempts = append(txAttempts, as.findTxAttempts(as.fatalErrored, txFilter, txAttemptFilter, txIDs...)...) } } return txAttempts } -// FetchTxs returns all transactions that match the given filters. +// FindTxs returns all transactions that match the given filters. // If txIDs are provided, only the transactions with those IDs are considered. // If no txIDs are provided, all transactions are considered. // If no txStates are provided, all transactions are considered. -func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FetchTxs( +func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxs( txStates []txmgrtypes.TxState, filter func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, txIDs ...int64, @@ -248,7 +249,7 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FetchT // if txStates is empty then apply the filter to only the as.allTransactions map if len(txStates) == 0 { - return as.fetchTxs(as.allTransactions, filter, txIDs...) + return as.findTxs(as.allTransactions, filter, txIDs...) } var txs []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] @@ -259,13 +260,13 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FetchT txs = append(txs, *as.inprogress) } case TxUnconfirmed: - txs = append(txs, as.fetchTxs(as.unconfirmed, filter, txIDs...)...) + txs = append(txs, as.findTxs(as.unconfirmed, filter, txIDs...)...) case TxConfirmedMissingReceipt: - txs = append(txs, as.fetchTxs(as.confirmedMissingReceipt, filter, txIDs...)...) + txs = append(txs, as.findTxs(as.confirmedMissingReceipt, filter, txIDs...)...) case TxConfirmed: - txs = append(txs, as.fetchTxs(as.confirmed, filter, txIDs...)...) + txs = append(txs, as.findTxs(as.confirmed, filter, txIDs...)...) case TxFatalError: - txs = append(txs, as.fetchTxs(as.fatalErrored, filter, txIDs...)...) + txs = append(txs, as.findTxs(as.fatalErrored, filter, txIDs...)...) } } @@ -383,7 +384,7 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) applyT } } -func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) fetchTxAttempts( +func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) findTxAttempts( txIDsToTx map[int64]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], txFilter func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, txAttemptFilter func(*txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, @@ -398,7 +399,8 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) fetchT for _, txID := range txIDs { tx := txIDsToTx[txID] if tx != nil && txFilter(tx) { - for _, txAttempt := range tx.TxAttempts { + for i := 0; i < len(tx.TxAttempts); i++ { + txAttempt := tx.TxAttempts[i] if txAttemptFilter(&txAttempt) { txAttempts = append(txAttempts, txAttempt) } @@ -411,7 +413,8 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) fetchT // if txIDs is empty then apply the filter to all transactions for _, tx := range txIDsToTx { if txFilter(tx) { - for _, txAttempt := range tx.TxAttempts { + for i := 0; i < len(tx.TxAttempts); i++ { + txAttempt := tx.TxAttempts[i] if txAttemptFilter(&txAttempt) { txAttempts = append(txAttempts, txAttempt) } @@ -422,7 +425,7 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) fetchT return txAttempts } -func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) fetchTxs( +func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) findTxs( txIDsToTx map[int64]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], filter func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool, txIDs ...int64, From ac445d76e838ebc477c0f57bf3873220e5199397 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 23 Feb 2024 13:48:18 -0500 Subject: [PATCH 07/84] fix naming --- common/txmgr/address_state.go | 56 +++++++++++++++++------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index 00d8396014f..d74ab7a0131 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -124,19 +124,19 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountT switch txState { case TxUnstarted: - return as.unstarted.Len() + return as.unstartedTxs.Len() case TxInProgress: - if as.inprogress != nil { + if as.inprogressTx != nil { return 1 } case TxUnconfirmed: - return len(as.unconfirmed) + return len(as.unconfirmedTxs) case TxConfirmedMissingReceipt: - return len(as.confirmedMissingReceipt) + return len(as.confirmedMissingReceiptTxs) case TxConfirmed: - return len(as.confirmed) + return len(as.confirmedTxs) case TxFatalError: - return len(as.fatalErrored) + return len(as.fatalErroredTxs) } return 0 @@ -165,24 +165,24 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ApplyT // if txStates is empty then apply the filter to only the as.allTransactions map if len(txStates) == 0 { - as.applyToTxs(as.allTransactions, fn, txIDs...) + as.applyToTxs(as.allTxs, fn, txIDs...) return } for _, txState := range txStates { switch txState { case TxInProgress: - if as.inprogress != nil { - fn(as.inprogress) + if as.inprogressTx != nil { + fn(as.inprogressTx) } case TxUnconfirmed: - as.applyToTxs(as.unconfirmed, fn, txIDs...) + as.applyToTxs(as.unconfirmedTxs, fn, txIDs...) case TxConfirmedMissingReceipt: - as.applyToTxs(as.confirmedMissingReceipt, fn, txIDs...) + as.applyToTxs(as.confirmedMissingReceiptTxs, fn, txIDs...) case TxConfirmed: - as.applyToTxs(as.confirmed, fn, txIDs...) + as.applyToTxs(as.confirmedTxs, fn, txIDs...) case TxFatalError: - as.applyToTxs(as.fatalErrored, fn, txIDs...) + as.applyToTxs(as.fatalErroredTxs, fn, txIDs...) } } } @@ -203,29 +203,29 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTx // if txStates is empty then apply the filter to only the as.allTransactions map if len(txStates) == 0 { - return as.findTxAttempts(as.allTransactions, txFilter, txAttemptFilter, txIDs...) + return as.findTxAttempts(as.allTxs, txFilter, txAttemptFilter, txIDs...) } var txAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] for _, txState := range txStates { switch txState { case TxInProgress: - if as.inprogress != nil && txFilter(as.inprogress) { - for i := 0; i < len(as.inprogress.TxAttempts); i++ { - txAttempt := as.inprogress.TxAttempts[i] + if as.inprogressTx != nil && txFilter(as.inprogressTx) { + for i := 0; i < len(as.inprogressTx.TxAttempts); i++ { + txAttempt := as.inprogressTx.TxAttempts[i] if txAttemptFilter(&txAttempt) { txAttempts = append(txAttempts, txAttempt) } } } case TxUnconfirmed: - txAttempts = append(txAttempts, as.findTxAttempts(as.unconfirmed, txFilter, txAttemptFilter, txIDs...)...) + txAttempts = append(txAttempts, as.findTxAttempts(as.unconfirmedTxs, txFilter, txAttemptFilter, txIDs...)...) case TxConfirmedMissingReceipt: - txAttempts = append(txAttempts, as.findTxAttempts(as.confirmedMissingReceipt, txFilter, txAttemptFilter, txIDs...)...) + txAttempts = append(txAttempts, as.findTxAttempts(as.confirmedMissingReceiptTxs, txFilter, txAttemptFilter, txIDs...)...) case TxConfirmed: - txAttempts = append(txAttempts, as.findTxAttempts(as.confirmed, txFilter, txAttemptFilter, txIDs...)...) + txAttempts = append(txAttempts, as.findTxAttempts(as.confirmedTxs, txFilter, txAttemptFilter, txIDs...)...) case TxFatalError: - txAttempts = append(txAttempts, as.findTxAttempts(as.fatalErrored, txFilter, txAttemptFilter, txIDs...)...) + txAttempts = append(txAttempts, as.findTxAttempts(as.fatalErroredTxs, txFilter, txAttemptFilter, txIDs...)...) } } @@ -246,24 +246,24 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTx // if txStates is empty then apply the filter to only the as.allTransactions map if len(txStates) == 0 { - return as.findTxs(as.allTransactions, filter, txIDs...) + return as.findTxs(as.allTxs, filter, txIDs...) } var txs []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] for _, txState := range txStates { switch txState { case TxInProgress: - if as.inprogress != nil && filter(as.inprogress) { - txs = append(txs, *as.inprogress) + if as.inprogressTx != nil && filter(as.inprogressTx) { + txs = append(txs, *as.inprogressTx) } case TxUnconfirmed: - txs = append(txs, as.findTxs(as.unconfirmed, filter, txIDs...)...) + txs = append(txs, as.findTxs(as.unconfirmedTxs, filter, txIDs...)...) case TxConfirmedMissingReceipt: - txs = append(txs, as.findTxs(as.confirmedMissingReceipt, filter, txIDs...)...) + txs = append(txs, as.findTxs(as.confirmedMissingReceiptTxs, filter, txIDs...)...) case TxConfirmed: - txs = append(txs, as.findTxs(as.confirmed, filter, txIDs...)...) + txs = append(txs, as.findTxs(as.confirmedTxs, filter, txIDs...)...) case TxFatalError: - txs = append(txs, as.findTxs(as.fatalErrored, filter, txIDs...)...) + txs = append(txs, as.findTxs(as.fatalErroredTxs, filter, txIDs...)...) } } From 0864c99b312da3e115235d6ab49730c9fb903ba6 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 26 Feb 2024 21:49:50 -0500 Subject: [PATCH 08/84] implement SetBroadcastBeforeBlockNum --- common/txmgr/inmemory_store.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index cd783a25210..f9fb1897360 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -192,6 +192,33 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Aband // SetBroadcastBeforeBlockNum sets the broadcast_before_block_num for a given chain ID func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SetBroadcastBeforeBlockNum(ctx context.Context, blockNum int64, chainID CHAIN_ID) error { + if ms.chainID.String() != chainID.String() { + return fmt.Errorf("set_broadcast_before_block_num: %w", ErrInvalidChainID) + } + + // Persist to persistent storage + if err := ms.txStore.SetBroadcastBeforeBlockNum(ctx, blockNum, chainID); err != nil { + return fmt.Errorf("set_broadcast_before_block_num: %w", err) + } + + fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { + if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + return + } + + for i := 0; i < len(tx.TxAttempts); i++ { + attempt := tx.TxAttempts[i] + if attempt.State == txmgrtypes.TxAttemptBroadcast && attempt.BroadcastBeforeBlockNum == nil { + tx.TxAttempts[i].BroadcastBeforeBlockNum = &blockNum + } + } + } + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + as.ApplyToTxsByState(nil, fn) + } + return nil } From 03f218fa5876120c0672bb5a57d83fef95d2e782 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 26 Feb 2024 21:53:20 -0500 Subject: [PATCH 09/84] implement UpdateBroadcastAts --- common/txmgr/inmemory_store.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index cd783a25210..bae91d0cca3 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -205,6 +205,24 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT // UpdateBroadcastAts updates the broadcast_at time for a given set of attempts func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateBroadcastAts(ctx context.Context, now time.Time, txIDs []int64) error { + // Persist to persistent storage + if err := ms.txStore.UpdateBroadcastAts(ctx, now, txIDs); err != nil { + return err + } + + // Update in memory store + fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { + if tx.BroadcastAt != nil && tx.BroadcastAt.Before(now) { + tx.BroadcastAt = &now + } + } + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + as.ApplyToTxsByState(nil, fn, txIDs...) + } + return nil } From c4baf9c0ffba82d2cfe97c21fbe8205a26e21543 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 26 Feb 2024 22:01:28 -0500 Subject: [PATCH 10/84] implement UpdateTxCallbackCompleted --- common/txmgr/inmemory_store.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index cd783a25210..3232ae5e988 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -229,6 +229,33 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateTxCallbackCompleted(ctx context.Context, pipelineTaskRunRid uuid.UUID, chainId CHAIN_ID) error { + if ms.chainID.String() != chainId.String() { + return fmt.Errorf("update_tx_callback_completed: %w", ErrInvalidChainID) + } + + // Persist to persistent storage + if err := ms.txStore.UpdateTxCallbackCompleted(ctx, pipelineTaskRunRid, chainId); err != nil { + return err + } + + // Update in memory store + fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { + if tx.PipelineTaskRunID.UUID == pipelineTaskRunRid { + tx.CallbackCompleted = true + } + } + wg := sync.WaitGroup{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + wg.Add(1) + go func(as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { + as.ApplyToTxsByState(nil, fn) + wg.Done() + }(as) + } + wg.Wait() + return nil } From 83ab7f5c33fb351c9b0f4fabf9ef72f2163909af Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 26 Feb 2024 22:03:26 -0500 Subject: [PATCH 11/84] implement SaveInProgressAttempt --- common/txmgr/inmemory_store.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index cd783a25210..10751f9390f 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -317,6 +317,39 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveC return nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveInProgressAttempt(ctx context.Context, attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[attempt.Tx.FromAddress] + if !ok { + return fmt.Errorf("save_in_progress_attempt: %w", ErrAddressNotFound) + } + if attempt.State != txmgrtypes.TxAttemptInProgress { + return fmt.Errorf("SaveInProgressAttempt failed: attempt state must be in_progress") + } + + // Persist to persistent storage + if err := ms.txStore.SaveInProgressAttempt(ctx, attempt); err != nil { + return err + } + + // Update in memory store + fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { + if tx.ID != attempt.TxID { + return + } + if tx.TxAttempts != nil && len(tx.TxAttempts) > 0 { + for i := 0; i < len(tx.TxAttempts); i++ { + if tx.TxAttempts[i].ID == attempt.ID { + tx.TxAttempts[i].State = txmgrtypes.TxAttemptInProgress + tx.TxAttempts[i].BroadcastBeforeBlockNum = attempt.BroadcastBeforeBlockNum + return + } + } + } + tx.TxAttempts = []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{*attempt} + } + as.ApplyToTxsByState(nil, fn, attempt.TxID) + return nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveInsufficientFundsAttempt(ctx context.Context, timeout time.Duration, attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], broadcastAt time.Time) error { From 83a78c721592ded134987021394de1ebfc7b2d83 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 26 Feb 2024 22:04:59 -0500 Subject: [PATCH 12/84] implement SaveInsufficientFundsAttempt --- common/txmgr/inmemory_store.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index cd783a25210..93026b54b10 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -320,6 +320,37 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveI return nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveInsufficientFundsAttempt(ctx context.Context, timeout time.Duration, attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], broadcastAt time.Time) error { + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[attempt.Tx.FromAddress] + if !ok { + return fmt.Errorf("save_insufficient_funds_attempt: %w", ErrAddressNotFound) + } + if !(attempt.State == txmgrtypes.TxAttemptInProgress || attempt.State == txmgrtypes.TxAttemptInsufficientFunds) { + return fmt.Errorf("expected state to be in_progress or insufficient_funds") + } + + // Persist to persistent storage + if err := ms.txStore.SaveInsufficientFundsAttempt(ctx, timeout, attempt, broadcastAt); err != nil { + return err + } + + // Update in memory store + fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { + if tx.ID != attempt.TxID { + return + } + if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + return + } + if tx.BroadcastAt.Before(broadcastAt) { + tx.BroadcastAt = &broadcastAt + } + + tx.TxAttempts[0].State = txmgrtypes.TxAttemptInsufficientFunds + } + as.ApplyToTxsByState(nil, fn, attempt.TxID) + return nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveSentAttempt(ctx context.Context, timeout time.Duration, attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], broadcastAt time.Time) error { From 24e18690a5d40b8454b379700fa6a21aac08803a Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 26 Feb 2024 22:06:32 -0500 Subject: [PATCH 13/84] implement SaveSentAttempt --- common/txmgr/inmemory_store.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index cd783a25210..af9afa36325 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -323,6 +323,38 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveI return nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveSentAttempt(ctx context.Context, timeout time.Duration, attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], broadcastAt time.Time) error { + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[attempt.Tx.FromAddress] + if !ok { + return fmt.Errorf("save_sent_attempt: %w", ErrAddressNotFound) + } + + if attempt.State != txmgrtypes.TxAttemptInProgress { + return fmt.Errorf("expected state to be in_progress") + } + + // Persist to persistent storage + if err := ms.txStore.SaveSentAttempt(ctx, timeout, attempt, broadcastAt); err != nil { + return err + } + + // Update in memory store + fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { + if tx.ID != attempt.TxID { + return + } + if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + return + } + if tx.BroadcastAt.Before(broadcastAt) { + tx.BroadcastAt = &broadcastAt + } + + tx.TxAttempts[0].State = txmgrtypes.TxAttemptBroadcast + } + as.ApplyToTxsByState(nil, fn, attempt.TxID) + return nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateTxForRebroadcast(ctx context.Context, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], etxAttempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { From 0f1c0ee53599321a5c95ab0e98a6cd303a961b1b Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 27 Feb 2024 15:42:09 -0500 Subject: [PATCH 14/84] implement close for address state --- common/txmgr/address_state.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index d74ab7a0131..7f37424a91e 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -359,6 +359,28 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) MoveCo return nil } +// Close releases all resources held by the address state. +func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close() { + clear(as.idempotencyKeyToTx) + + as.unstartedTxs.Close() + as.unstartedTxs = nil + as.inprogressTx = nil + + clear(as.unconfirmedTxs) + clear(as.confirmedMissingReceiptTxs) + clear(as.confirmedTxs) + clear(as.allTxs) + clear(as.fatalErroredTxs) + + as.idempotencyKeyToTx = nil + as.unconfirmedTxs = nil + as.confirmedMissingReceiptTxs = nil + as.confirmedTxs = nil + as.allTxs = nil + as.fatalErroredTxs = nil +} + func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) applyToTxs( txIDsToTx map[int64]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], fn func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]), From 46af61e90980e8668d937bfe415e8129bc274f5e Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 27 Feb 2024 15:43:38 -0500 Subject: [PATCH 15/84] implement abandon --- common/txmgr/address_state.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index 7f37424a91e..3e0511c6143 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -381,6 +381,39 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close( as.fatalErroredTxs = nil } +func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Abandon() { + as.Lock() + defer as.Unlock() + + for as.unstartedTxs.Len() > 0 { + tx := as.unstartedTxs.RemoveNextTx() + as.abandonTx(tx) + } + + if as.inprogressTx != nil { + tx := as.inprogressTx + as.abandonTx(tx) + as.inprogressTx = nil + } + for _, tx := range as.unconfirmedTxs { + as.abandonTx(tx) + } + + clear(as.unconfirmedTxs) +} + +func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) abandonTx(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { + if tx == nil { + return + } + + tx.State = TxFatalError + tx.Sequence = nil + tx.Error = null.NewString("abandoned", true) + + as.fatalErroredTxs[tx.ID] = tx +} + func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) applyToTxs( txIDsToTx map[int64]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], fn func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]), From 6a1477b83da7d90fbc43204cfdfed7939845bd5a Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 27 Feb 2024 15:45:32 -0500 Subject: [PATCH 16/84] implement Abandon --- common/txmgr/inmemory_store.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index cd783a25210..23e1c2b6175 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -187,6 +187,24 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close // Abandon removes all transactions for a given address func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Abandon(ctx context.Context, chainID CHAIN_ID, addr ADDR) error { + if ms.chainID.String() != chainID.String() { + return fmt.Errorf("abandon: %w", ErrInvalidChainID) + } + + // Mark all persisted transactions as abandoned + if err := ms.txStore.Abandon(ctx, chainID, addr); err != nil { + return err + } + + // check that the address exists in the unstarted transactions + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[addr] + if !ok { + return fmt.Errorf("abandon: %w", ErrAddressNotFound) + } + as.Abandon() + return nil } From 9cfa388933a7f1f80b97da7ad4f27484836383b0 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 27 Feb 2024 15:45:59 -0500 Subject: [PATCH 17/84] implement Close --- common/txmgr/inmemory_store.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 23e1c2b6175..754ba1706f1 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -183,6 +183,16 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Updat // Close closes the InMemoryStore func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close() { + // Close the event recorder + ms.txStore.Close() + + // Clear all address states + ms.addressStatesLock.Lock() + for _, as := range ms.addressStates { + as.Close() + } + clear(ms.addressStates) + ms.addressStatesLock.Unlock() } // Abandon removes all transactions for a given address From 7f614e4ab7231db7acd2d10aec21f88f581afa37 Mon Sep 17 00:00:00 2001 From: James Walker Date: Wed, 28 Feb 2024 22:29:40 -0500 Subject: [PATCH 18/84] implement read only in memory store methods --- common/txmgr/inmemory_store.go | 814 ++++++++++++++++++++++++++++++++- 1 file changed, 791 insertions(+), 23 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index cd783a25210..e451ee77968 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -2,9 +2,11 @@ package txmgr import ( "context" + "encoding/json" "errors" "fmt" "math/big" + "sort" "sync" "time" @@ -12,6 +14,7 @@ import ( "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/v2/common/chains/label" feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/common/types" @@ -108,11 +111,43 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Creat // FindTxWithIdempotencyKey returns a transaction with the given idempotency key func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxWithIdempotencyKey(ctx context.Context, idempotencyKey string, chainID CHAIN_ID) (*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_tx_with_idempotency_key: %w", ErrInvalidChainID) + } + + // Check if the transaction is in the pending queue of all address states + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + if tx := as.FindTxWithIdempotencyKey(idempotencyKey); tx != nil { + return ms.deepCopyTx(*tx), nil + } + } + + return nil, fmt.Errorf("find_tx_with_idempotency_key: %w", ErrTxnNotFound) } // CheckTxQueueCapacity checks if the queue capacity has been reached for a given address func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CheckTxQueueCapacity(ctx context.Context, fromAddress ADDR, maxQueuedTransactions uint64, chainID CHAIN_ID) error { + if maxQueuedTransactions == 0 { + return nil + } + if ms.chainID.String() != chainID.String() { + return fmt.Errorf("check_tx_queue_capacity: %w", ErrInvalidChainID) + } + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[fromAddress] + if !ok { + return fmt.Errorf("check_tx_queue_capacity: %w", ErrAddressNotFound) + } + + count := uint64(as.CountTransactionsByState(TxUnstarted)) + if count >= maxQueuedTransactions { + return fmt.Errorf("check_tx_queue_capacity: cannot create transaction; too many unstarted transactions in the queue (%v/%v). %s", count, maxQueuedTransactions, label.MaxQueuedTransactionsWarning) + } + return nil } @@ -120,21 +155,44 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Check // It is used to initialize the in-memory sequence map in the broadcaster // TODO(jtw): this is until we have a abstracted Sequencer Component which can be used instead func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindLatestSequence(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (seq SEQ, err error) { - return seq, nil + // Query the persistent store + return ms.txStore.FindLatestSequence(ctx, fromAddress, chainID) } // CountUnconfirmedTransactions returns the number of unconfirmed transactions for a given address. // Unconfirmed transactions are transactions that have been broadcast but not confirmed on-chain. // NOTE(jtw): used to calculate total inflight transactions func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountUnconfirmedTransactions(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (uint32, error) { - return 0, nil + if ms.chainID.String() != chainID.String() { + return 0, fmt.Errorf("count_unstarted_transactions: %w", ErrInvalidChainID) + } + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[fromAddress] + if !ok { + return 0, fmt.Errorf("count_unstarted_transactions: %w", ErrAddressNotFound) + } + + return uint32(as.CountTransactionsByState(TxUnconfirmed)), nil } // CountUnstartedTransactions returns the number of unstarted transactions for a given address. // Unstarted transactions are transactions that have not been broadcast yet. // NOTE(jtw): used to calculate total inflight transactions func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountUnstartedTransactions(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (uint32, error) { - return 0, nil + if ms.chainID.String() != chainID.String() { + return 0, fmt.Errorf("count_unstarted_transactions: %w", ErrInvalidChainID) + } + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[fromAddress] + if !ok { + return 0, fmt.Errorf("count_unstarted_transactions: %w", ErrAddressNotFound) + } + + return uint32(as.CountTransactionsByState(TxUnstarted)), nil } // UpdateTxUnstartedToInProgress updates a transaction from unstarted to in_progress. @@ -200,7 +258,42 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error, ) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_next_unstarted_transaction_from_address: %w", ErrInvalidChainID) + } + + txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return tx.TxAttempts != nil && len(tx.TxAttempts) > 0 + } + txAttemptFilter := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return true + } + states := []txmgrtypes.TxState{TxConfirmedMissingReceipt} + attempts := []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + attempts = append(attempts, as.FindTxAttempts(states, txFilter, txAttemptFilter)...) + } + // TODO: FINISH THIS + // sort by tx_id ASC, gas_price DESC, gas_tip_cap DESC + sort.SliceStable(attempts, func(i, j int) bool { + /* + if attempts[i].TxID == attempts[j].TxID { + // sort by gas_price DESC + } + */ + + return attempts[i].TxID < attempts[j].TxID + }) + + // deep copy the attempts + var eAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + for _, attempt := range attempts { + eAttempts = append(eAttempts, ms.deepCopyTxAttempt(attempt.Tx, attempt)) + } + + return eAttempts, nil } // UpdateBroadcastAts updates the broadcast_at time for a given set of attempts @@ -218,14 +311,92 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT attempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error, ) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return attempts, fmt.Errorf("find_tx_attempts_requiring_receipt_fetch: %w", ErrInvalidChainID) + } + + txFilterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return tx.TxAttempts != nil && len(tx.TxAttempts) > 0 + } + txAttemptFilterFn := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return attempt.State != txmgrtypes.TxAttemptInsufficientFunds + } + states := []txmgrtypes.TxState{TxUnconfirmed, TxConfirmedMissingReceipt} + attempts = []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + attempts = append(attempts, as.FindTxAttempts(states, txFilterFn, txAttemptFilterFn)...) + } + // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC + sort.Slice(attempts, func(i, j int) bool { + return (*attempts[i].Tx.Sequence).Int64() < (*attempts[j].Tx.Sequence).Int64() + }) + + // deep copy the attempts + var eAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + for _, attempt := range attempts { + eAttempts = append(eAttempts, ms.deepCopyTxAttempt(attempt.Tx, attempt)) + } + + return eAttempts, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesPendingCallback(ctx context.Context, blockNum int64, chainID CHAIN_ID) ( []txmgrtypes.ReceiptPlus[R], error, ) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_txes_pending_callback: %w", ErrInvalidChainID) + } + + filterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + return false + } + + // TODO: loop through all attempts since any of them can have a receipt + if tx.TxAttempts[0].Receipts == nil || len(tx.TxAttempts[0].Receipts) == 0 { + return false + } + + if tx.PipelineTaskRunID.Valid && tx.SignalCallback && !tx.CallbackCompleted && + tx.TxAttempts[0].Receipts[0].GetBlockNumber() != nil && + big.NewInt(blockNum-int64(tx.MinConfirmations.Uint32)).Cmp(tx.TxAttempts[0].Receipts[0].GetBlockNumber()) > 0 { + return true + } + + return false + + } + states := []txmgrtypes.TxState{TxConfirmed} + txs := []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + txs = append(txs, as.FindTxs(states, filterFn)...) + } + + receiptsPlus := make([]txmgrtypes.ReceiptPlus[R], len(txs)) + meta := map[string]interface{}{} + for i, tx := range txs { + if err := json.Unmarshal(json.RawMessage(*tx.Meta), &meta); err != nil { + return nil, err + } + failOnRevert := false + if v, ok := meta["FailOnRevert"].(bool); ok { + failOnRevert = v + } + + receiptsPlus[i] = txmgrtypes.ReceiptPlus[R]{ + ID: tx.PipelineTaskRunID.UUID, + Receipt: (tx.TxAttempts[0].Receipts[0]).(R), + FailOnRevert: failOnRevert, + } + clear(meta) + } + + return receiptsPlus, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateTxCallbackCompleted(ctx context.Context, pipelineTaskRunRid uuid.UUID, chainId CHAIN_ID) error { @@ -240,18 +411,176 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error, ) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_txes_by_meta_field_and_states: %w", ErrInvalidChainID) + } + + filterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.Meta == nil { + return false + } + meta := map[string]interface{}{} + if err := json.Unmarshal(json.RawMessage(*tx.Meta), &meta); err != nil { + return false + } + if v, ok := meta[metaField].(string); ok { + return v == metaValue + } + + return false + } + txsLock := sync.Mutex{} + txs := []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + wg := sync.WaitGroup{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + wg.Add(1) + go func(as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { + for _, tx := range as.FindTxs(states, filterFn) { + etx := ms.deepCopyTx(tx) + txsLock.Lock() + txs = append(txs, etx) + txsLock.Unlock() + } + wg.Done() + }(as) + } + wg.Wait() + + return txs, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []txmgrtypes.TxState, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_txes_with_meta_field_by_states: %w", ErrInvalidChainID) + } + + filterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.Meta == nil { + return false + } + meta := map[string]interface{}{} + if err := json.Unmarshal(json.RawMessage(*tx.Meta), &meta); err != nil { + return false + } + if _, ok := meta[metaField]; ok { + return true + } + + return false + } + + txsLock := sync.Mutex{} + txs := []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + wg := sync.WaitGroup{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + wg.Add(1) + go func(as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { + for _, tx := range as.FindTxs(states, filterFn) { + etx := ms.deepCopyTx(tx) + txsLock.Lock() + txs = append(txs, etx) + txsLock.Unlock() + } + wg.Done() + }(as) + } + wg.Wait() + + return txs, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_txes_with_meta_field_by_receipt_block_num: %w", ErrInvalidChainID) + } + + filterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.Meta == nil { + return false + } + meta := map[string]interface{}{} + if err := json.Unmarshal(json.RawMessage(*tx.Meta), &meta); err != nil { + return false + } + if _, ok := meta[metaField]; !ok { + return false + } + if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + return false + } + + for _, attempt := range tx.TxAttempts { + if attempt.Receipts == nil || len(attempt.Receipts) == 0 { + continue + } + if attempt.Receipts[0].GetBlockNumber() == nil { + continue + } + return attempt.Receipts[0].GetBlockNumber().Int64() >= blockNum + } + + return false + } + + txsLock := sync.Mutex{} + txs := []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + wg := sync.WaitGroup{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + wg.Add(1) + go func(as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { + for _, tx := range as.FindTxs(nil, filterFn) { + etx := ms.deepCopyTx(tx) + txsLock.Lock() + txs = append(txs, etx) + txsLock.Unlock() + } + wg.Done() + }(as) + } + wg.Wait() + + return txs, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithAttemptsAndReceiptsByIdsAndState(ctx context.Context, ids []big.Int, states []txmgrtypes.TxState, chainID *big.Int) (tx []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_txes_with_attempts_and_receipts_by_ids_and_state: %w", ErrInvalidChainID) + } + + filterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return true + } + + txIDs := make([]int64, len(ids)) + for i, id := range ids { + txIDs[i] = id.Int64() + } + + txsLock := sync.Mutex{} + txs := []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + wg := sync.WaitGroup{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + wg.Add(1) + go func(as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { + for _, tx := range as.FindTxs(states, filterFn, txIDs...) { + etx := ms.deepCopyTx(tx) + txsLock.Lock() + txs = append(txs, etx) + txsLock.Unlock() + } + wg.Done() + }(as) + } + wg.Wait() + + return txs, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) PruneUnstartedTxQueue(ctx context.Context, queueSize uint32, subject uuid.UUID) ([]int64, error) { @@ -262,7 +591,18 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ReapT return nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountTransactionsByState(_ context.Context, state txmgrtypes.TxState, chainID CHAIN_ID) (uint32, error) { - return 0, nil + if ms.chainID.String() != chainID.String() { + return 0, fmt.Errorf("count_transactions_by_state: %w", ErrInvalidChainID) + } + + var total int + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + total += as.CountTransactionsByState(state) + } + + return uint32(total), nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) DeleteInProgressAttempt(ctx context.Context, attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { @@ -270,47 +610,390 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Delet } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxsRequiringResubmissionDueToInsufficientFunds(_ context.Context, address ADDR, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_txs_requiring_resubmission_due_to_insufficient_funds: %w", ErrInvalidChainID) + } + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[address] + if !ok { + return nil, fmt.Errorf("find_txs_requiring_resubmission_due_to_insufficient_funds: %w", ErrAddressNotFound) + } + + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + return false + } + for _, attempt := range tx.TxAttempts { + if attempt.State == txmgrtypes.TxAttemptInsufficientFunds { + return true + } + } + return false + } + states := []txmgrtypes.TxState{TxUnconfirmed} + txs := as.FindTxs(states, filter) + // sort by sequence ASC + sort.Slice(txs, func(i, j int) bool { + return (*txs[i].Sequence).Int64() < (*txs[j].Sequence).Int64() + }) + + etxs := make([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], len(txs)) + for i, tx := range txs { + etxs[i] = ms.deepCopyTx(tx) + } + + return etxs, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxAttemptsRequiringResend(_ context.Context, olderThan time.Time, maxInFlightTransactions uint32, chainID CHAIN_ID, address ADDR) ([]txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_tx_attempts_requiring_resend: %w", ErrInvalidChainID) + } + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[address] + if !ok { + return nil, fmt.Errorf("find_tx_attempts_requiring_resend: %w", ErrAddressNotFound) + } + + txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + return false + } + return tx.BroadcastAt.Before(olderThan) || tx.BroadcastAt.Equal(olderThan) + } + txAttemptFilter := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return attempt.State != txmgrtypes.TxAttemptInProgress + } + states := []txmgrtypes.TxState{TxUnconfirmed, TxConfirmedMissingReceipt} + attempts := as.FindTxAttempts(states, txFilter, txAttemptFilter) + // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC + sort.Slice(attempts, func(i, j int) bool { + return (*attempts[i].Tx.Sequence).Int64() < (*attempts[j].Tx.Sequence).Int64() + }) + // LIMIT by maxInFlightTransactions + if len(attempts) > int(maxInFlightTransactions) { + attempts = attempts[:maxInFlightTransactions] + } + + // deep copy the attempts + var eAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + for _, attempt := range attempts { + eAttempts = append(eAttempts, ms.deepCopyTxAttempt(attempt.Tx, attempt)) + } + + return eAttempts, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxWithSequence(_ context.Context, fromAddress ADDR, seq SEQ) (*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[fromAddress] + if !ok { + return nil, fmt.Errorf("find_tx_with_sequence: %w", ErrAddressNotFound) + } + + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.Sequence == nil { + return false + } + + return (*tx.Sequence).String() == seq.String() + } + states := []txmgrtypes.TxState{TxConfirmed, TxConfirmedMissingReceipt, TxUnconfirmed} + txs := as.FindTxs(states, filter) + if len(txs) == 0 { + return nil, nil + } + + return ms.deepCopyTx(txs[0]), nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTransactionsConfirmedInBlockRange(_ context.Context, highBlockNumber, lowBlockNumber int64, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_transactions_confirmed_in_block_range: %w", ErrInvalidChainID) + } + + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + return false + } + for _, attempt := range tx.TxAttempts { + if attempt.State != txmgrtypes.TxAttemptBroadcast { + continue + } + if len(attempt.Receipts) == 0 { + continue + } + if attempt.Receipts[0].GetBlockNumber() == nil { + continue + } + blockNum := attempt.Receipts[0].GetBlockNumber().Int64() + if blockNum >= lowBlockNumber && blockNum <= highBlockNumber { + return true + } + } + + return false + } + states := []txmgrtypes.TxState{TxConfirmed, TxConfirmedMissingReceipt} + txsLock := sync.Mutex{} + txs := []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + wg := sync.WaitGroup{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + wg.Add(1) + go func(as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { + ts := as.FindTxs(states, filter) + txsLock.Lock() + txs = append(txs, ts...) + txsLock.Unlock() + wg.Done() + }(as) + } + wg.Wait() + // sort by sequence ASC + sort.Slice(txs, func(i, j int) bool { + return (*txs[i].Sequence).Int64() < (*txs[j].Sequence).Int64() + }) + + etxs := make([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], len(txs)) + for i, tx := range txs { + etxs[i] = ms.deepCopyTx(tx) + } + + return etxs, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindEarliestUnconfirmedBroadcastTime(ctx context.Context, chainID CHAIN_ID) (null.Time, error) { - return null.Time{}, nil + if ms.chainID.String() != chainID.String() { + return null.Time{}, fmt.Errorf("find_earliest_unconfirmed_broadcast_time: %w", ErrInvalidChainID) + } + + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return tx.InitialBroadcastAt != nil + } + states := []txmgrtypes.TxState{TxUnconfirmed} + txsLock := sync.Mutex{} + txs := []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + wg := sync.WaitGroup{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + wg.Add(1) + go func(as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { + etxs := as.FindTxs(states, filter) + txsLock.Lock() + txs = append(txs, etxs...) + txsLock.Unlock() + wg.Done() + }(as) + } + wg.Wait() + + var minInitialBroadcastAt time.Time + for _, tx := range txs { + if tx.InitialBroadcastAt.Before(minInitialBroadcastAt) { + minInitialBroadcastAt = *tx.InitialBroadcastAt + } + } + + return null.TimeFrom(minInitialBroadcastAt), nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context, chainID CHAIN_ID) (null.Int, error) { - return null.Int{}, nil + if ms.chainID.String() != chainID.String() { + return null.Int{}, fmt.Errorf("find_earliest_unconfirmed_broadcast_time: %w", ErrInvalidChainID) + } + + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + return false + } + + for _, attempt := range tx.TxAttempts { + if attempt.BroadcastBeforeBlockNum != nil { + return true + } + } + + return false + } + states := []txmgrtypes.TxState{TxUnconfirmed} + txsLock := sync.Mutex{} + txs := []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + wg := sync.WaitGroup{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + wg.Add(1) + go func(as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { + etxs := as.FindTxs(states, filter) + txsLock.Lock() + txs = append(txs, etxs...) + txsLock.Unlock() + wg.Done() + }(as) + } + wg.Wait() + + var minBroadcastBeforeBlockNum int64 + for _, tx := range txs { + if *tx.TxAttempts[0].BroadcastBeforeBlockNum < minBroadcastBeforeBlockNum { + minBroadcastBeforeBlockNum = *tx.TxAttempts[0].BroadcastBeforeBlockNum + } + } + + return null.IntFrom(minBroadcastBeforeBlockNum), nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetInProgressTxAttempts(ctx context.Context, address ADDR, chainID CHAIN_ID) ([]txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("get_in_progress_tx_attempts: %w", ErrInvalidChainID) + } + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[address] + if !ok { + return nil, fmt.Errorf("get_in_progress_tx_attempts: %w", ErrAddressNotFound) + } + + txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return tx.TxAttempts != nil && len(tx.TxAttempts) > 0 + } + txAttemptFilter := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return attempt.State == txmgrtypes.TxAttemptInProgress + } + states := []txmgrtypes.TxState{TxConfirmed, TxConfirmedMissingReceipt, TxUnconfirmed} + attempts := as.FindTxAttempts(states, txFilter, txAttemptFilter) + + // deep copy the attempts + var eAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + for _, attempt := range attempts { + eAttempts = append(eAttempts, ms.deepCopyTxAttempt(attempt.Tx, attempt)) + } + + return eAttempts, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetNonFatalTransactions(ctx context.Context, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("get_non_fatal_transactions: %w", ErrInvalidChainID) + } + + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return tx.State != TxFatalError + } + txsLock := sync.Mutex{} + txs := []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + wg := sync.WaitGroup{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + wg.Add(1) + go func(as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { + etxs := as.FindTxs(nil, filter) + txsLock.Lock() + txs = append(txs, etxs...) + txsLock.Unlock() + wg.Done() + }(as) + } + wg.Wait() + + etxs := make([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], len(txs)) + for i, tx := range txs { + etxs[i] = ms.deepCopyTx(tx) + } + + return etxs, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetTxByID(_ context.Context, id int64) (*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return tx.ID == id + } + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + txs := as.FindTxs(nil, filter, id) + if len(txs) > 0 { + return ms.deepCopyTx(txs[0]), nil + } + } + + return nil, fmt.Errorf("failed to get tx with id: %v", id) } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) HasInProgressTransaction(_ context.Context, account ADDR, chainID CHAIN_ID) (bool, error) { - return false, nil + if ms.chainID.String() != chainID.String() { + return false, fmt.Errorf("has_in_progress_transaction: %w", ErrInvalidChainID) + } + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[account] + if !ok { + return false, fmt.Errorf("has_in_progress_transaction: %w", ErrAddressNotFound) + } + + n := as.CountTransactionsByState(TxInProgress) + + return n > 0, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) LoadTxAttempts(_ context.Context, etx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[etx.FromAddress] + if !ok { + return fmt.Errorf("load_tx_attempts: %w", ErrAddressNotFound) + } + + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return tx.ID == etx.ID + } + txAttempts := []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + for _, tx := range as.FindTxs(nil, filter, etx.ID) { + for _, txAttempt := range tx.TxAttempts { + txAttempts = append(txAttempts, ms.deepCopyTxAttempt(*etx, txAttempt)) + } + } + etx.TxAttempts = txAttempts + return nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) PreloadTxes(_ context.Context, attempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { + if len(attempts) == 0 { + return nil + } + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[attempts[0].Tx.FromAddress] + if !ok { + return fmt.Errorf("preload_txes: %w", ErrAddressNotFound) + } + + txIDs := make([]int64, len(attempts)) + for i, attempt := range attempts { + txIDs[i] = attempt.TxID + } + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return true + } + txs := as.FindTxs(nil, filter, txIDs...) + for i, attempt := range attempts { + for _, tx := range txs { + if tx.ID == attempt.TxID { + attempts[i].Tx = *ms.deepCopyTx(tx) + } + } + } + return nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveConfirmedMissingReceiptAttempt(ctx context.Context, timeout time.Duration, attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], broadcastAt time.Time) error { @@ -329,11 +1012,96 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Updat return nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) IsTxFinalized(ctx context.Context, blockHeight int64, txID int64, chainID CHAIN_ID) (bool, error) { + if ms.chainID.String() != chainID.String() { + return false, fmt.Errorf("is_tx_finalized: %w", ErrInvalidChainID) + } + + txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.ID != txID { + return false + } + + for _, attempt := range tx.TxAttempts { + if attempt.Receipts == nil || len(attempt.Receipts) == 0 { + continue + } + // there can only be one receipt per attempt + if attempt.Receipts[0].GetBlockNumber() == nil { + continue + } + + return attempt.Receipts[0].GetBlockNumber().Int64() <= (blockHeight - int64(tx.MinConfirmations.Uint32)) + } + + return false + } + txAttemptFilter := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return attempt.Receipts != nil && len(attempt.Receipts) > 0 + } + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + txas := as.FindTxAttempts(nil, txFilter, txAttemptFilter, txID) + if len(txas) > 0 { + return true, nil + } + } + return false, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxsRequiringGasBump(ctx context.Context, address ADDR, blockNum, gasBumpThreshold, depth int64, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - return nil, nil + if ms.chainID.String() != chainID.String() { + return nil, fmt.Errorf("find_txs_requiring_gas_bump: %w", ErrInvalidChainID) + } + if gasBumpThreshold == 0 { + return nil, nil + } + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[address] + if !ok { + return nil, fmt.Errorf("find_txs_requiring_gas_bump: %w", ErrAddressNotFound) + } + + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + return false + } + attempt := tx.TxAttempts[0] + if *attempt.BroadcastBeforeBlockNum <= blockNum || + attempt.State == txmgrtypes.TxAttemptBroadcast { + return false + } + + if tx.State != TxUnconfirmed || + attempt.ID != 0 { + return false + } + + return true + } + states := []txmgrtypes.TxState{TxUnconfirmed} + txs := as.FindTxs(states, filter) + // sort by sequence ASC + sort.Slice(txs, func(i, j int) bool { + return (*txs[i].Sequence).Int64() < (*txs[j].Sequence).Int64() + }) + + if depth > 0 { + // LIMIT by depth + if len(txs) > int(depth) { + txs = txs[:depth] + } + } + + etxs := make([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], len(txs)) + for i, tx := range txs { + etxs[i] = ms.deepCopyTx(tx) + } + + return etxs, nil } func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) MarkAllConfirmedMissingReceipt(ctx context.Context, chainID CHAIN_ID) error { return nil From 07d399b6593fad289d006b2a4b8f66a16c9dea62 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 4 Mar 2024 17:15:45 -0500 Subject: [PATCH 19/84] return -1 for unknown states --- common/txmgr/address_state.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index 3e0511c6143..b1a4caa4d56 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -129,6 +129,7 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountT if as.inprogressTx != nil { return 1 } + return 0 case TxUnconfirmed: return len(as.unconfirmedTxs) case TxConfirmedMissingReceipt: @@ -139,7 +140,7 @@ func (as *AddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountT return len(as.fatalErroredTxs) } - return 0 + return -1 } // FindTxWithIdempotencyKey returns the transaction with the given idempotency key. From c37666b1cda28fa7e7d8eaf6e362cffa81502bcc Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 4 Mar 2024 18:43:14 -0500 Subject: [PATCH 20/84] add tests for FindTxWithIdempotencyKey --- common/txmgr/inmemory_store.go | 4 +- .../evm/txmgr/evm_inmemory_store_test.go | 87 +++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index d5ca7ee6ad3..b6bae5d6571 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -113,7 +113,7 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Creat // FindTxWithIdempotencyKey returns a transaction with the given idempotency key func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxWithIdempotencyKey(ctx context.Context, idempotencyKey string, chainID CHAIN_ID) (*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("find_tx_with_idempotency_key: %w", ErrInvalidChainID) + return nil, nil } // Check if the transaction is in the pending queue of all address states @@ -125,7 +125,7 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT } } - return nil, fmt.Errorf("find_tx_with_idempotency_key: %w", ErrTxnNotFound) + return nil, nil } // CheckTxQueueCapacity checks if the queue capacity has been reached for a given address diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a102ee1c996..81023bb9840 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,14 +1,101 @@ package txmgr_test import ( + "context" + "math/big" "testing" + "github.com/ethereum/go-ethereum/common" + //"github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" + + // evmassets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + /* + toAddress := testutils.NewAddress() + gasLimit := uint32(1000) + payload := []byte{1, 2, 3} + */ + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + idempotencyKey := "777" + inTx := cltest.NewEthTx(fromAddress) + inTx.IdempotencyKey = &idempotencyKey + // insert the transaction into the persistent store + require.NoError(t, persistentStore.InsertTx(&inTx)) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + tcs := []struct { + name string + inIdempotencyKey string + inChainID *big.Int + + expNilErr bool + expNilTx bool + }{ + {"no idempotency key", "", chainID, true, true}, + {"wrong idempotency key", "wrong", chainID, true, true}, + {"finds tx with idempotency key", idempotencyKey, chainID, false, false}, + {"wrong chain", idempotencyKey, big.NewInt(999), true, true}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + ctx := testutils.Context(t) + actTx, actErr := inMemoryStore.FindTxWithIdempotencyKey(ctx, tc.inIdempotencyKey, tc.inChainID) + expTx, expErr := persistentStore.FindTxWithIdempotencyKey(ctx, tc.inIdempotencyKey, tc.inChainID) + require.Equal(t, expErr, actErr) + if tc.expNilErr { + require.Nil(t, actErr) + require.Nil(t, expErr) + } + if tc.expNilTx { + require.Nil(t, actTx) + require.Nil(t, expTx) + } else { + require.NotNil(t, actTx) + require.NotNil(t, expTx) + assertTxEqual(t, *expTx, *actTx) + } + }) + } +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 4acd0967e56101debb60006321fde39b609d2601 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 5 Mar 2024 15:15:10 -0500 Subject: [PATCH 21/84] add test for CheckTxQueueCapacity --- common/txmgr/inmemory_store.go | 6 +- .../evm/txmgr/evm_inmemory_store_test.go | 89 ++++++++++++++++--- 2 files changed, 79 insertions(+), 16 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index b6bae5d6571..4dfbf175e1c 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -134,19 +134,19 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Check return nil } if ms.chainID.String() != chainID.String() { - return fmt.Errorf("check_tx_queue_capacity: %w", ErrInvalidChainID) + return nil } ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() as, ok := ms.addressStates[fromAddress] if !ok { - return fmt.Errorf("check_tx_queue_capacity: %w", ErrAddressNotFound) + return nil } count := uint64(as.CountTransactionsByState(TxUnstarted)) if count >= maxQueuedTransactions { - return fmt.Errorf("check_tx_queue_capacity: cannot create transaction; too many unstarted transactions in the queue (%v/%v). %s", count, maxQueuedTransactions, label.MaxQueuedTransactionsWarning) + return fmt.Errorf("cannot create transaction; too many unstarted transactions in the queue (%v/%v). %s", count, maxQueuedTransactions, label.MaxQueuedTransactionsWarning) } return nil diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 81023bb9840..03aa7a4ca52 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -23,6 +23,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +// TODO: CLEANUP? + func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { t.Parallel() @@ -30,13 +32,7 @@ func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) persistentStore := cltest.NewTestTxStore(t, db, dbcfg) kst := cltest.NewKeyStore(t, db, dbcfg) - _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) - /* - toAddress := testutils.NewAddress() - gasLimit := uint32(1000) - payload := []byte{1, 2, 3} - */ ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) @@ -65,8 +61,8 @@ func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { inIdempotencyKey string inChainID *big.Int - expNilErr bool - expNilTx bool + hasErr bool + hasTx bool }{ {"no idempotency key", "", chainID, true, true}, {"wrong idempotency key", "wrong", chainID, true, true}, @@ -80,18 +76,85 @@ func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { actTx, actErr := inMemoryStore.FindTxWithIdempotencyKey(ctx, tc.inIdempotencyKey, tc.inChainID) expTx, expErr := persistentStore.FindTxWithIdempotencyKey(ctx, tc.inIdempotencyKey, tc.inChainID) require.Equal(t, expErr, actErr) - if tc.expNilErr { + if !tc.hasErr { require.Nil(t, actErr) require.Nil(t, expErr) } - if tc.expNilTx { - require.Nil(t, actTx) - require.Nil(t, expTx) - } else { + if tc.hasTx { require.NotNil(t, actTx) require.NotNil(t, expTx) assertTxEqual(t, *expTx, *actTx) + } else { + require.Nil(t, actTx) + require.Nil(t, expTx) + } + }) + } +} + +func TestInMemoryStore_CheckTxQueueCapacity(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + inTxs := []evmtxmgr.Tx{ + cltest.NewEthTx(fromAddress), + cltest.NewEthTx(fromAddress), + } + for _, inTx := range inTxs { + // insert the transaction into the persistent store + require.NoError(t, persistentStore.InsertTx(&inTx)) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + } + + tcs := []struct { + name string + inFromAddress common.Address + inMaxQueuedTxs uint64 + inChainID *big.Int + + hasErr bool + }{ + {"capacity reached", fromAddress, 2, chainID, true}, + {"above capacity", fromAddress, 1, chainID, true}, + {"below capacity", fromAddress, 3, chainID, false}, + {"wrong chain", fromAddress, 2, big.NewInt(999), false}, + {"wrong address", common.Address{}, 2, chainID, false}, + {"max queued txs is 0", fromAddress, 0, chainID, false}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + ctx := testutils.Context(t) + actErr := inMemoryStore.CheckTxQueueCapacity(ctx, tc.inFromAddress, tc.inMaxQueuedTxs, tc.inChainID) + expErr := persistentStore.CheckTxQueueCapacity(ctx, tc.inFromAddress, tc.inMaxQueuedTxs, tc.inChainID) + if tc.hasErr { + require.NotNil(t, expErr) + require.NotNil(t, actErr) + } else { + require.NoError(t, expErr) + require.NoError(t, actErr) } + //require.Equal(t, expErr, actErr) }) } } From 0ef6869877f1fed8d06bf54375ee649470e6f741 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 5 Mar 2024 15:36:55 -0500 Subject: [PATCH 22/84] implement tests for CountUnstartedTransactions and CountUnconfirmedTransactions --- common/txmgr/inmemory_store.go | 8 +- .../evm/txmgr/evm_inmemory_store_test.go | 88 ++++++++++++++++++- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 4dfbf175e1c..f3bde5b2064 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -165,14 +165,14 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindL // NOTE(jtw): used to calculate total inflight transactions func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountUnconfirmedTransactions(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (uint32, error) { if ms.chainID.String() != chainID.String() { - return 0, fmt.Errorf("count_unstarted_transactions: %w", ErrInvalidChainID) + return 0, nil } ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() as, ok := ms.addressStates[fromAddress] if !ok { - return 0, fmt.Errorf("count_unstarted_transactions: %w", ErrAddressNotFound) + return 0, nil } return uint32(as.CountTransactionsByState(TxUnconfirmed)), nil @@ -183,14 +183,14 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Count // NOTE(jtw): used to calculate total inflight transactions func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountUnstartedTransactions(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (uint32, error) { if ms.chainID.String() != chainID.String() { - return 0, fmt.Errorf("count_unstarted_transactions: %w", ErrInvalidChainID) + return 0, nil } ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() as, ok := ms.addressStates[fromAddress] if !ok { - return 0, fmt.Errorf("count_unstarted_transactions: %w", ErrAddressNotFound) + return 0, nil } return uint32(as.CountTransactionsByState(TxUnstarted)), nil diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 03aa7a4ca52..b482f488e4a 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -154,7 +154,93 @@ func TestInMemoryStore_CheckTxQueueCapacity(t *testing.T) { require.NoError(t, expErr) require.NoError(t, actErr) } - //require.Equal(t, expErr, actErr) + }) + } +} + +func TestInMemoryStore_CountTransactions(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + // initialize unstarted transactions + inUnstartedTxs := []evmtxmgr.Tx{ + cltest.NewEthTx(fromAddress), + cltest.NewEthTx(fromAddress), + } + for _, inTx := range inUnstartedTxs { + // insert the transaction into the persistent store + require.NoError(t, persistentStore.InsertTx(&inTx)) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + } + // initialize unconfirmed transactions + inNonces := []int64{1, 2, 3} + for _, inNonce := range inNonces { + // insert the transaction into the persistent store + inTx := cltest.MustInsertUnconfirmedEthTx(t, persistentStore, inNonce, fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + } + + tcs := []struct { + name string + inFromAddress common.Address + inChainID *big.Int + + expUnstartedCount uint32 + expUnconfirmedCount uint32 + hasErr bool + }{ + {"return correct total transactions", fromAddress, chainID, 2, 3, false}, + {"invalid chain id", fromAddress, big.NewInt(999), 0, 0, false}, + {"invalid address", common.Address{}, chainID, 0, 0, false}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + ctx := testutils.Context(t) + actMemoryCount, actErr := inMemoryStore.CountUnstartedTransactions(ctx, tc.inFromAddress, tc.inChainID) + actPersistentCount, expErr := persistentStore.CountUnstartedTransactions(ctx, tc.inFromAddress, tc.inChainID) + if tc.hasErr { + require.NotNil(t, expErr) + require.NotNil(t, actErr) + } else { + require.NoError(t, expErr) + require.NoError(t, actErr) + } + assert.Equal(t, tc.expUnstartedCount, actMemoryCount) + assert.Equal(t, tc.expUnstartedCount, actPersistentCount) + + actMemoryCount, actErr = inMemoryStore.CountUnconfirmedTransactions(ctx, tc.inFromAddress, tc.inChainID) + actPersistentCount, expErr = persistentStore.CountUnconfirmedTransactions(ctx, tc.inFromAddress, tc.inChainID) + if tc.hasErr { + require.NotNil(t, expErr) + require.NotNil(t, actErr) + } else { + require.NoError(t, expErr) + require.NoError(t, actErr) + } + assert.Equal(t, tc.expUnconfirmedCount, actMemoryCount) + assert.Equal(t, tc.expUnconfirmedCount, actPersistentCount) }) } } From 1bd8ac4d7f93a2fa9cfa9309fd50acfdfa45b9b5 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 5 Mar 2024 21:30:55 -0500 Subject: [PATCH 23/84] implment tests for FindTxAttemptsConfirmedMissingReceipt --- common/txmgr/inmemory_store.go | 17 ++-- .../evm/txmgr/evm_inmemory_store_test.go | 79 +++++++++++++++++++ 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index f3bde5b2064..c0c1b9e1974 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -18,6 +18,7 @@ import ( feetypes "github.com/smartcontractkit/chainlink/v2/common/fee/types" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/common/types" + evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" ) var ( @@ -260,7 +261,7 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT error, ) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("find_next_unstarted_transaction_from_address: %w", ErrInvalidChainID) + return nil, nil } txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { @@ -276,14 +277,20 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT for _, as := range ms.addressStates { attempts = append(attempts, as.FindTxAttempts(states, txFilter, txAttemptFilter)...) } - // TODO: FINISH THIS // sort by tx_id ASC, gas_price DESC, gas_tip_cap DESC sort.SliceStable(attempts, func(i, j int) bool { - /* - if attempts[i].TxID == attempts[j].TxID { + if attempts[i].TxID == attempts[j].TxID { + var iGasPrice, jGasPrice evmgas.EvmFee + // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee + + if iGasPrice.Legacy.Cmp(jGasPrice.Legacy) == 0 { + // sort by gas_tip_cap DESC + return iGasPrice.DynamicFeeCap.Cmp(jGasPrice.DynamicFeeCap) > 0 + } else { // sort by gas_price DESC + return iGasPrice.Legacy.Cmp(jGasPrice.Legacy) > 0 } - */ + } return attempts[i].TxID < attempts[j].TxID }) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index b482f488e4a..ce5bdaf5bbc 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -2,8 +2,10 @@ package txmgr_test import ( "context" + "fmt" "math/big" "testing" + "time" "github.com/ethereum/go-ethereum/common" //"github.com/google/uuid" @@ -245,6 +247,83 @@ func TestInMemoryStore_CountTransactions(t *testing.T) { } } +func TestInMemoryStore_FindTxAttemptsConfirmedMissingReceipt(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + // initialize transactions + inTxDatas := []struct { + nonce int64 + broadcastBeforeBlockNum int64 + broadcastAt time.Time + }{ + {0, 1, time.Unix(1616509300, 0)}, + {1, 1, time.Unix(1616509400, 0)}, + {2, 1, time.Unix(1616509500, 0)}, + } + for _, inTxData := range inTxDatas { + fmt.Println("DATA", inTxData.nonce, inTxData.broadcastBeforeBlockNum, inTxData.broadcastAt) + // insert the transaction into the persistent store + inTx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, persistentStore, inTxData.nonce, inTxData.broadcastBeforeBlockNum, + inTxData.broadcastAt, fromAddress, + ) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + } + + tcs := []struct { + name string + inChainID *big.Int + + expTxAttemptsCount int + hasError bool + }{ + {"finds tx attempts confirmed missing receipt", chainID, 3, false}, + {"wrong chain", big.NewInt(999), 0, false}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + ctx := testutils.Context(t) + actTxAttempts, actErr := inMemoryStore.FindTxAttemptsConfirmedMissingReceipt(ctx, tc.inChainID) + expTxAttempts, expErr := persistentStore.FindTxAttemptsConfirmedMissingReceipt(ctx, tc.inChainID) + if tc.hasError { + require.NotNil(t, actErr) + require.NotNil(t, expErr) + } else { + require.NoError(t, actErr) + require.NoError(t, expErr) + require.Equal(t, tc.expTxAttemptsCount, len(expTxAttempts)) + require.Equal(t, tc.expTxAttemptsCount, len(actTxAttempts)) + for i := 0; i < len(expTxAttempts); i++ { + assertTxAttemptEqual(t, expTxAttempts[i], actTxAttempts[i]) + } + } + + }) + } +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 04e5930afe90ca0f7d5c0cc66b0843c1ce46459e Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 5 Mar 2024 21:49:30 -0500 Subject: [PATCH 24/84] add comment --- common/txmgr/inmemory_store.go | 6 ++++++ core/chains/evm/txmgr/evm_inmemory_store_test.go | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index c0c1b9e1974..d4757eef672 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -337,7 +337,13 @@ func (ms *InMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT attempts = append(attempts, as.FindTxAttempts(states, txFilterFn, txAttemptFilterFn)...) } // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC + // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee sort.Slice(attempts, func(i, j int) bool { + iSequence, jSequence := attempts[i].Tx.Sequence, attempts[j].Tx.Sequence + if iSequence == nil || jSequence == nil { + return false + } + return (*attempts[i].Tx.Sequence).Int64() < (*attempts[j].Tx.Sequence).Int64() }) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index ce5bdaf5bbc..68fba742e2a 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -319,7 +319,6 @@ func TestInMemoryStore_FindTxAttemptsConfirmedMissingReceipt(t *testing.T) { assertTxAttemptEqual(t, expTxAttempts[i], actTxAttempts[i]) } } - }) } } From 10195bfc8ec1456d644c097aa3ec9549336ffee2 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 7 Mar 2024 16:19:24 -0500 Subject: [PATCH 25/84] address comments --- common/txmgr/address_state.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index 1a0c131009c..216e6ff0627 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -146,7 +146,7 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) countT return len(as.fatalErroredTxs) } - return -1 + panic("countTransactionByState: unknown transaction state") } // findTxWithIdempotencyKey returns the transaction with the given idempotency key. @@ -190,6 +190,8 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) applyT as.applyToTxs(as.confirmedTxs, fn, txIDs...) case TxFatalError: as.applyToTxs(as.fatalErroredTxs, fn, txIDs...) + default: + panic("applyToTxsByState: unknown transaction state") } } } @@ -233,6 +235,8 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) findTx txAttempts = append(txAttempts, as._findTxAttempts(as.confirmedTxs, txFilter, txAttemptFilter, txIDs...)...) case TxFatalError: txAttempts = append(txAttempts, as._findTxAttempts(as.fatalErroredTxs, txFilter, txAttemptFilter, txIDs...)...) + default: + panic("findTxAttempts: unknown transaction state") } } @@ -271,6 +275,8 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) findTx txs = append(txs, as._findTxs(as.confirmedTxs, filter, txIDs...)...) case TxFatalError: txs = append(txs, as._findTxs(as.fatalErroredTxs, filter, txIDs...)...) + default: + panic("findTxs: unknown transaction state") } } From d9b19c156cc58bbf865eaa6c917cea2904d19630 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 7 Mar 2024 16:20:34 -0500 Subject: [PATCH 26/84] change convention --- common/txmgr/address_state.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index 216e6ff0627..f1f783b166a 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -144,9 +144,9 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) countT return len(as.confirmedTxs) case TxFatalError: return len(as.fatalErroredTxs) + default: + panic("countTransactionByState: unknown transaction state") } - - panic("countTransactionByState: unknown transaction state") } // findTxWithIdempotencyKey returns the transaction with the given idempotency key. From 80b511777744f9079b2aa12012598548c1c7c7c7 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 7 Mar 2024 16:21:35 -0500 Subject: [PATCH 27/84] unexport more methods --- common/txmgr/address_state.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index f1f783b166a..6c3f37a52ac 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -358,8 +358,8 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) moveCo return nil } -// Close releases all resources held by the address state. -func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close() { +// close releases all resources held by the address state. +func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) close() { clear(as.idempotencyKeyToTx) as.unstartedTxs.Close() @@ -380,7 +380,7 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close( as.fatalErroredTxs = nil } -func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Abandon() { +func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) abandon() { as.Lock() defer as.Unlock() From 0343a0be257b59483c455a0cac5b928eb635d907 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 7 Mar 2024 16:23:22 -0500 Subject: [PATCH 28/84] make sure unsafe methods have _ --- common/txmgr/address_state.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index 6c3f37a52ac..350540d0164 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -172,7 +172,7 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) applyT // if txStates is empty then apply the filter to only the as.allTransactions map if len(txStates) == 0 { - as.applyToTxs(as.allTxs, fn, txIDs...) + as._applyToTxs(as.allTxs, fn, txIDs...) return } @@ -183,13 +183,13 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) applyT fn(as.inprogressTx) } case TxUnconfirmed: - as.applyToTxs(as.unconfirmedTxs, fn, txIDs...) + as._applyToTxs(as.unconfirmedTxs, fn, txIDs...) case TxConfirmedMissingReceipt: - as.applyToTxs(as.confirmedMissingReceiptTxs, fn, txIDs...) + as._applyToTxs(as.confirmedMissingReceiptTxs, fn, txIDs...) case TxConfirmed: - as.applyToTxs(as.confirmedTxs, fn, txIDs...) + as._applyToTxs(as.confirmedTxs, fn, txIDs...) case TxFatalError: - as.applyToTxs(as.fatalErroredTxs, fn, txIDs...) + as._applyToTxs(as.fatalErroredTxs, fn, txIDs...) default: panic("applyToTxsByState: unknown transaction state") } @@ -386,22 +386,23 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) abando for as.unstartedTxs.Len() > 0 { tx := as.unstartedTxs.RemoveNextTx() - as.abandonTx(tx) + as._abandonTx(tx) } if as.inprogressTx != nil { tx := as.inprogressTx - as.abandonTx(tx) + as._abandonTx(tx) as.inprogressTx = nil } for _, tx := range as.unconfirmedTxs { - as.abandonTx(tx) + as._abandonTx(tx) } clear(as.unconfirmedTxs) } -func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) abandonTx(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { +// This method is not concurrent safe and should only be called from within a lock +func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) _abandonTx(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { if tx == nil { return } @@ -413,7 +414,8 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) abando as.fatalErroredTxs[tx.ID] = tx } -func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) applyToTxs( +// This method is not concurrent safe and should only be called from within a lock +func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) _applyToTxs( txIDsToTx map[int64]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], fn func(*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]), txIDs ...int64, From 7a35db5a0cf7fcc2af4c02ce0dbe5f981871f900 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 8 Mar 2024 16:05:00 -0500 Subject: [PATCH 29/84] clean FindTxWithIdempotencyKey --- core/chains/evm/txmgr/evm_inmemory_store_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 68fba742e2a..b218359d89a 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -25,8 +25,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) -// TODO: CLEANUP? - func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { t.Parallel() @@ -66,10 +64,10 @@ func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { hasErr bool hasTx bool }{ - {"no idempotency key", "", chainID, true, true}, - {"wrong idempotency key", "wrong", chainID, true, true}, - {"finds tx with idempotency key", idempotencyKey, chainID, false, false}, - {"wrong chain", idempotencyKey, big.NewInt(999), true, true}, + {"no idempotency key", "", chainID, false, false}, + {"wrong idempotency key", "wrong", chainID, false, false}, + {"finds tx with idempotency key", idempotencyKey, chainID, false, true}, + {"wrong chain", idempotencyKey, big.NewInt(999), false, false}, } for _, tc := range tcs { From c2a19498e9c94f2722b4bff3ee5de07617445bc8 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 8 Mar 2024 16:12:00 -0500 Subject: [PATCH 30/84] some test cleanup --- .../evm/txmgr/evm_inmemory_store_test.go | 76 ++++++++++++++----- 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index b218359d89a..dd3efabb27f 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -158,7 +158,7 @@ func TestInMemoryStore_CheckTxQueueCapacity(t *testing.T) { } } -func TestInMemoryStore_CountTransactions(t *testing.T) { +func TestInMemoryStore_CountUnstartedTransactions(t *testing.T) { t.Parallel() db := pgtest.NewSqlxDB(t) @@ -192,27 +192,18 @@ func TestInMemoryStore_CountTransactions(t *testing.T) { // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) } - // initialize unconfirmed transactions - inNonces := []int64{1, 2, 3} - for _, inNonce := range inNonces { - // insert the transaction into the persistent store - inTx := cltest.MustInsertUnconfirmedEthTx(t, persistentStore, inNonce, fromAddress) - // insert the transaction into the in-memory store - require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - } tcs := []struct { name string inFromAddress common.Address inChainID *big.Int - expUnstartedCount uint32 - expUnconfirmedCount uint32 - hasErr bool + expUnstartedCount uint32 + hasErr bool }{ - {"return correct total transactions", fromAddress, chainID, 2, 3, false}, - {"invalid chain id", fromAddress, big.NewInt(999), 0, 0, false}, - {"invalid address", common.Address{}, chainID, 0, 0, false}, + {"return correct total transactions", fromAddress, chainID, 2, false}, + {"invalid chain id", fromAddress, big.NewInt(999), 0, false}, + {"invalid address", common.Address{}, chainID, 0, false}, } for _, tc := range tcs { @@ -229,9 +220,60 @@ func TestInMemoryStore_CountTransactions(t *testing.T) { } assert.Equal(t, tc.expUnstartedCount, actMemoryCount) assert.Equal(t, tc.expUnstartedCount, actPersistentCount) + }) + } +} + +func TestInMemoryStore_CountUnconfirmedTransactions(t *testing.T) { + t.Parallel() - actMemoryCount, actErr = inMemoryStore.CountUnconfirmedTransactions(ctx, tc.inFromAddress, tc.inChainID) - actPersistentCount, expErr = persistentStore.CountUnconfirmedTransactions(ctx, tc.inFromAddress, tc.inChainID) + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + // initialize unconfirmed transactions + inNonces := []int64{1, 2, 3} + for _, inNonce := range inNonces { + // insert the transaction into the persistent store + inTx := cltest.MustInsertUnconfirmedEthTx(t, persistentStore, inNonce, fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + } + + tcs := []struct { + name string + inFromAddress common.Address + inChainID *big.Int + + expUnconfirmedCount uint32 + hasErr bool + }{ + {"return correct total transactions", fromAddress, chainID, 3, false}, + {"invalid chain id", fromAddress, big.NewInt(999), 0, false}, + {"invalid address", common.Address{}, chainID, 0, false}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + ctx := testutils.Context(t) + actMemoryCount, actErr := inMemoryStore.CountUnconfirmedTransactions(ctx, tc.inFromAddress, tc.inChainID) + actPersistentCount, expErr := persistentStore.CountUnconfirmedTransactions(ctx, tc.inFromAddress, tc.inChainID) if tc.hasErr { require.NotNil(t, expErr) require.NotNil(t, actErr) From 9ca93e0f0b668186025158455664724dc4917c0c Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 11 Mar 2024 10:00:33 -0400 Subject: [PATCH 31/84] remove unused pkgs --- core/chains/evm/txmgr/evm_inmemory_store_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index dd3efabb27f..1c184479a10 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -8,14 +8,12 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - //"github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/logger" commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" - // evmassets "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" From 950a9b15be044b61d193d36b84cea243766b69fe Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 11 Mar 2024 16:55:23 -0400 Subject: [PATCH 32/84] add filtering for unstarted Txs --- common/txmgr/address_state.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index 7a9866f1bb7..ed327b69960 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -267,6 +267,14 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) findTx var txs []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] for _, txState := range txStates { switch txState { + case TxUnstarted: + filter2 := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if tx.State != TxUnstarted { + return false + } + return filter(tx) + } + txs = append(txs, as._findTxs(as.allTxs, filter2, txIDs...)...) case TxInProgress: if as.inprogressTx != nil && filter(as.inprogressTx) { txs = append(txs, *as.inprogressTx) From 6f4ee885792c48a41903d0ceea832010df6d47ba Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 11 Mar 2024 20:19:29 -0400 Subject: [PATCH 33/84] add tests for SaveInProgressAttempt --- common/txmgr/inmemory_store.go | 18 ++- .../evm/txmgr/evm_inmemory_store_test.go | 127 +++++++++++++++++- 2 files changed, 143 insertions(+), 2 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index eafba6c7bb9..b95a7475aed 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -319,7 +319,23 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveC func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveInProgressAttempt(ctx context.Context, attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() - as, ok := ms.addressStates[attempt.Tx.FromAddress] + + var tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + for _, as := range ms.addressStates { + fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return true + } + txs := as.findTxs(nil, fn, attempt.TxID) + if len(txs) != 0 { + tx = &txs[0] + break + } + } + if tx == nil { + return fmt.Errorf("save_in_progress_attempt: %w: with attempt hash %q", ErrTxnNotFound, attempt.Hash) + } + + as, ok := ms.addressStates[tx.FromAddress] if !ok { return fmt.Errorf("save_in_progress_attempt: %w", ErrAddressNotFound) } diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a102ee1c996..ea725cb9f9a 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,14 +1,140 @@ package txmgr_test import ( + "context" + "math/big" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + + evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_SaveInProgressAttempt(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("saves new in_progress attempt if attempt is new", func(t *testing.T) { + // Insert a transaction into persistent store + inTx := cltest.MustInsertUnconfirmedEthTx(t, persistentStore, 1, fromAddress) + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + // generate new attempt + inTxAttempt := cltest.NewLegacyEthTxAttempt(t, inTx.ID) + require.Equal(t, int64(0), inTxAttempt.ID) + + err := inMemoryStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) + require.NoError(t, err) + + expTxAttempt, err := persistentStore.FindTxAttempt(inTxAttempt.Hash) + require.NoError(t, err) + assert.Equal(t, txmgrtypes.TxAttemptInProgress, expTxAttempt.State) + + // Check that the in-memory store has the new attempt + fn := func(tx *evmtxmgr.Tx) bool { return true } + txs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.NotNil(t, txs) + require.Equal(t, 1, len(txs)) + + for _, actTxAttempt := range txs[0].TxAttempts { + if actTxAttempt.Hash == expTxAttempt.Hash { + assertTxAttemptEqual(t, *expTxAttempt, actTxAttempt) + } + } + }) + t.Run("updates old attempt to in_progress when insufficient_funds", func(t *testing.T) { + // Insert a transaction into persistent store + inTx := mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, persistentStore, 23, fromAddress) + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + // use old attempt + inTxAttempt := inTx.TxAttempts[0] + require.Equal(t, txmgrtypes.TxAttemptInsufficientFunds, inTxAttempt.State) + require.NotEqual(t, int64(0), inTxAttempt.ID) + + inTxAttempt.BroadcastBeforeBlockNum = nil + inTxAttempt.State = txmgrtypes.TxAttemptInProgress + err := inMemoryStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) + require.NoError(t, err) + + expTxAttempt, err := persistentStore.FindTxAttempt(inTxAttempt.Hash) + require.NoError(t, err) + assert.Equal(t, txmgrtypes.TxAttemptInProgress, expTxAttempt.State) + + // Check that the in-memory store has the new attempt + fn := func(tx *evmtxmgr.Tx) bool { return true } + txs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.NotNil(t, txs) + require.Equal(t, 1, len(txs)) + + for _, actTxAttempt := range txs[0].TxAttempts { + if actTxAttempt.Hash == expTxAttempt.Hash { + assertTxAttemptEqual(t, *expTxAttempt, actTxAttempt) + } + } + }) + t.Run("handles errors the same way as the persistent store", func(t *testing.T) { + // Insert a transaction into persistent store + inTx := cltest.MustInsertUnconfirmedEthTx(t, persistentStore, 55, fromAddress) + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + // generate new attempt + inTxAttempt := cltest.NewLegacyEthTxAttempt(t, inTx.ID) + require.Equal(t, int64(0), inTxAttempt.ID) + + // wrong tx id + inTxAttempt.TxID = 999 + actErr := inMemoryStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) + expErr := persistentStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) + assert.Error(t, actErr) + assert.Error(t, expErr) + inTxAttempt.TxID = inTx.ID // reset + + // wrong state + inTxAttempt.State = txmgrtypes.TxAttemptBroadcast + actErr = inMemoryStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) + expErr = persistentStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) + assert.Error(t, actErr) + assert.Error(t, expErr) + assert.Equal(t, expErr, actErr) + inTxAttempt.State = txmgrtypes.TxAttemptInProgress // reset + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) @@ -42,7 +168,6 @@ func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { func assertTxAttemptEqual(t *testing.T, exp, act evmtxmgr.TxAttempt) { assert.Equal(t, exp.ID, act.ID) assert.Equal(t, exp.TxID, act.TxID) - assert.Equal(t, exp.Tx, act.Tx) assert.Equal(t, exp.TxFee, act.TxFee) assert.Equal(t, exp.ChainSpecificFeeLimit, act.ChainSpecificFeeLimit) assert.Equal(t, exp.SignedRawTx, act.SignedRawTx) From 9e7298a8933c29b716de97d32a9a0c1ca9e289fe Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 11 Mar 2024 22:08:05 -0400 Subject: [PATCH 34/84] add tests for SaveSentAttempt --- common/txmgr/inmemory_store.go | 26 ++++-- .../evm/txmgr/evm_inmemory_store_test.go | 81 +++++++++++++++++++ 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index fe2c0fcd63a..04b207a78f6 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -325,9 +325,20 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveI func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveSentAttempt(ctx context.Context, timeout time.Duration, attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], broadcastAt time.Time) error { ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() - as, ok := ms.addressStates[attempt.Tx.FromAddress] - if !ok { - return fmt.Errorf("save_sent_attempt: %w", ErrAddressNotFound) + + var tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { return true } + for _, vas := range ms.addressStates { + txs := vas.findTxs(nil, filter, attempt.TxID) + if len(txs) != 0 { + tx = &txs[0] + as = vas + break + } + } + if tx == nil { + return fmt.Errorf("save_sent_attempt: %w", ErrTxnNotFound) } if attempt.State != txmgrtypes.TxAttemptInProgress { @@ -347,11 +358,16 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveS if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { return } - if tx.BroadcastAt.Before(broadcastAt) { + if tx.BroadcastAt != nil && tx.BroadcastAt.Before(broadcastAt) { tx.BroadcastAt = &broadcastAt } - tx.TxAttempts[0].State = txmgrtypes.TxAttemptBroadcast + for i := range tx.TxAttempts { + if tx.TxAttempts[i].ID == attempt.ID { + tx.TxAttempts[i].State = txmgrtypes.TxAttemptBroadcast + return + } + } } as.applyToTxsByState(nil, fn, attempt.TxID) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a102ee1c996..f080c4bd12a 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,14 +1,95 @@ package txmgr_test import ( + "context" + "math/big" "testing" + "time" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + + evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_SaveSentAttempt(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + defaultDuration := time.Second * 5 + t.Run("updates attempt state to broadcast and checks error returns", func(t *testing.T) { + // Insert a transaction into persistent store + inTx := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 1, fromAddress) + require.Nil(t, inTx.BroadcastAt) + now := time.Now() + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + err := inMemoryStore.SaveSentAttempt( + testutils.Context(t), + defaultDuration, + &inTx.TxAttempts[0], + now, + ) + require.NoError(t, err) + + expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + require.NoError(t, err) + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.Equal(t, 1, len(actTxs)) + actTx := actTxs[0] + assertTxEqual(t, expTx, actTx) + assert.Equal(t, txmgrtypes.TxAttemptBroadcast, actTx.TxAttempts[0].State) + + // wrong tx id + inTx.TxAttempts[0].TxID = 123 + actErr := inMemoryStore.SaveSentAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + expErr := persistentStore.SaveSentAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + assert.Error(t, actErr) + assert.Error(t, expErr) + inTx.TxAttempts[0].TxID = inTx.ID // reset + + // wrong attempt state + inTx.TxAttempts[0].State = txmgrtypes.TxAttemptBroadcast + actErr = inMemoryStore.SaveSentAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + expErr = persistentStore.SaveSentAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + assert.Error(t, actErr) + assert.Error(t, expErr) + inTx.TxAttempts[0].State = txmgrtypes.TxAttemptInProgress // reset + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From a864b27dfeb55c4208e8513953e7bfe3655f093a Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 11 Mar 2024 22:42:50 -0400 Subject: [PATCH 35/84] add tests for abandon --- .../evm/txmgr/evm_inmemory_store_test.go | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a102ee1c996..a8d1c2f4745 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,14 +1,82 @@ package txmgr_test import ( + "context" + "math/big" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" + + evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_Abandon(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("saves new in_progress attempt if attempt is new", func(t *testing.T) { + nTxs := 3 + // Insert a transaction into persistent store + for i := 0; i < nTxs; i++ { + inTx := cltest.NewEthTx(fromAddress) + // insert the transaction into the persistent store + require.NoError(t, persistentStore.InsertTx(&inTx)) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + } + + actErr := inMemoryStore.Abandon(testutils.Context(t), chainID, fromAddress) + expErr := persistentStore.Abandon(testutils.Context(t), chainID, fromAddress) + require.NoError(t, actErr) + require.NoError(t, expErr) + + expTxs, err := persistentStore.FindTxesByFromAddressAndState(ctx, fromAddress, "fatal_error") + require.NoError(t, err) + require.NotNil(t, expTxs) + require.Equal(t, nTxs, len(expTxs)) + + // Check that the in-memory store has the new attempt + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn) + require.NotNil(t, actTxs) + require.Equal(t, nTxs, len(actTxs)) + + for i := 0; i < nTxs; i++ { + assertTxEqual(t, *expTxs[i], actTxs[i]) + } + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 075b1f713ca6be8702dac7f64cd8c098342966ca Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 11 Mar 2024 23:11:15 -0400 Subject: [PATCH 36/84] add tests for SaveInsufficientFundsAttempt --- common/txmgr/inmemory_store.go | 26 ++++-- .../evm/txmgr/evm_inmemory_store_test.go | 80 +++++++++++++++++++ 2 files changed, 101 insertions(+), 5 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 7cf23ca047c..0a4a21f18db 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -322,10 +322,22 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveI func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveInsufficientFundsAttempt(ctx context.Context, timeout time.Duration, attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], broadcastAt time.Time) error { ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() - as, ok := ms.addressStates[attempt.Tx.FromAddress] - if !ok { - return fmt.Errorf("save_insufficient_funds_attempt: %w", ErrAddressNotFound) + + var tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] + filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { return true } + for _, vas := range ms.addressStates { + txs := vas.findTxs(nil, filter, attempt.TxID) + if len(txs) > 0 { + tx = &txs[0] + as = vas + break + } + } + if tx == nil { + return nil } + if !(attempt.State == txmgrtypes.TxAttemptInProgress || attempt.State == txmgrtypes.TxAttemptInsufficientFunds) { return fmt.Errorf("expected state to be in_progress or insufficient_funds") } @@ -343,11 +355,15 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveI if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { return } - if tx.BroadcastAt.Before(broadcastAt) { + if tx.BroadcastAt != nil && tx.BroadcastAt.Before(broadcastAt) { tx.BroadcastAt = &broadcastAt } - tx.TxAttempts[0].State = txmgrtypes.TxAttemptInsufficientFunds + for i := 0; i < len(tx.TxAttempts); i++ { + if tx.TxAttempts[i].ID == attempt.ID { + tx.TxAttempts[i].State = txmgrtypes.TxAttemptInsufficientFunds + } + } } as.applyToTxsByState(nil, fn, attempt.TxID) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a102ee1c996..57d9614520d 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,14 +1,94 @@ package txmgr_test import ( + "context" + "math/big" "testing" + "time" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + + evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_SaveInsufficientFundsAttempt(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + defaultDuration := time.Second * 5 + t.Run("updates attempt state and checks error returns", func(t *testing.T) { + // Insert a transaction into persistent store + inTx := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 1, fromAddress) + now := time.Now() + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + err := inMemoryStore.SaveInsufficientFundsAttempt( + testutils.Context(t), + defaultDuration, + &inTx.TxAttempts[0], + now, + ) + require.NoError(t, err) + + expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + require.NoError(t, err) + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.Equal(t, 1, len(actTxs)) + actTx := actTxs[0] + assertTxEqual(t, expTx, actTx) + assert.Equal(t, txmgrtypes.TxAttemptInsufficientFunds, actTx.TxAttempts[0].State) + + // wrong tx id + inTx.TxAttempts[0].TxID = 123 + actErr := inMemoryStore.SaveInsufficientFundsAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + expErr := persistentStore.SaveInsufficientFundsAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + assert.NoError(t, actErr) + assert.NoError(t, expErr) + inTx.TxAttempts[0].TxID = inTx.ID // reset + + // wrong attempt state + inTx.TxAttempts[0].State = txmgrtypes.TxAttemptBroadcast + actErr = inMemoryStore.SaveInsufficientFundsAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + expErr = persistentStore.SaveInsufficientFundsAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + assert.Error(t, actErr) + assert.Error(t, expErr) + inTx.TxAttempts[0].State = txmgrtypes.TxAttemptInsufficientFunds // reset + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From de560a096afe21786c851c29447781d3d53ae77a Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 11 Mar 2024 23:31:58 -0400 Subject: [PATCH 37/84] add tests for UpdateBroadcastAts --- .../evm/txmgr/evm_inmemory_store_test.go | 97 ++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a102ee1c996..12a462e9806 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,14 +1,106 @@ package txmgr_test import ( + "context" + "math/big" "testing" + "time" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" + + evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_UpdateBroadcastAts(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("does not update when broadcast_at is Null", func(t *testing.T) { + // Insert a transaction into persistent store + inTx := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 1, fromAddress) + require.Nil(t, inTx.BroadcastAt) + now := time.Now() + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + err := inMemoryStore.UpdateBroadcastAts( + testutils.Context(t), + now, + []int64{inTx.ID}, + ) + require.NoError(t, err) + + expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + require.NoError(t, err) + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.Equal(t, 1, len(actTxs)) + actTx := actTxs[0] + assertTxEqual(t, expTx, actTx) + assert.Nil(t, actTx.BroadcastAt) + }) + + t.Run("updates broadcast_at when not null", func(t *testing.T) { + // Insert a transaction into persistent store + time1 := time.Now() + inTx := cltest.NewEthTx(fromAddress) + inTx.Sequence = new(evmtypes.Nonce) + inTx.State = commontxmgr.TxUnconfirmed + inTx.BroadcastAt = &time1 + inTx.InitialBroadcastAt = &time1 + require.NoError(t, persistentStore.InsertTx(&inTx)) + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + time2 := time1.Add(1 * time.Hour) + err := inMemoryStore.UpdateBroadcastAts( + testutils.Context(t), + time2, + []int64{inTx.ID}, + ) + require.NoError(t, err) + + expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + require.NoError(t, err) + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.Equal(t, 1, len(actTxs)) + actTx := actTxs[0] + assertTxEqual(t, expTx, actTx) + assert.NotNil(t, actTx.BroadcastAt) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) @@ -20,7 +112,10 @@ func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.Value, act.Value) assert.Equal(t, exp.FeeLimit, act.FeeLimit) assert.Equal(t, exp.Error, act.Error) - assert.Equal(t, exp.BroadcastAt, act.BroadcastAt) + if exp.BroadcastAt != nil { + require.NotNil(t, act.BroadcastAt) + assert.Equal(t, exp.BroadcastAt.Unix(), act.BroadcastAt.Unix()) + } assert.Equal(t, exp.InitialBroadcastAt, act.InitialBroadcastAt) assert.Equal(t, exp.CreatedAt, act.CreatedAt) assert.Equal(t, exp.State, act.State) From 918f2dfaedca32547e7200eac7796867a16bd664 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 11 Mar 2024 23:51:42 -0400 Subject: [PATCH 38/84] implement tests for updateTxCallbackCompleted --- common/txmgr/inmemory_store.go | 2 +- .../evm/txmgr/evm_inmemory_store_test.go | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 96f84dc8d2d..c9a2d2cf896 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -230,7 +230,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateTxCallbackCompleted(ctx context.Context, pipelineTaskRunRid uuid.UUID, chainId CHAIN_ID) error { if ms.chainID.String() != chainId.String() { - return fmt.Errorf("update_tx_callback_completed: %w", ErrInvalidChainID) + return nil } // Persist to persistent storage diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a102ee1c996..b7c1901156a 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,14 +1,90 @@ package txmgr_test import ( + "context" + "math/big" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" + + evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_UpdateTxCallbackCompleted(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("sets tx callback as completed", func(t *testing.T) { + // Insert a transaction into persistent store + inTx := cltest.NewEthTx(fromAddress) + inTx.PipelineTaskRunID = uuid.NullUUID{UUID: uuid.New(), Valid: true} + require.NoError(t, persistentStore.InsertTx(&inTx)) + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + err := inMemoryStore.UpdateTxCallbackCompleted( + testutils.Context(t), + inTx.PipelineTaskRunID.UUID, + chainID, + ) + require.NoError(t, err) + + expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + require.NoError(t, err) + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.Equal(t, 1, len(actTxs)) + actTx := actTxs[0] + assertTxEqual(t, expTx, actTx) + assert.True(t, actTx.CallbackCompleted) + + // wrong chain id + wrongChainID := big.NewInt(123) + actErr := inMemoryStore.UpdateTxCallbackCompleted(testutils.Context(t), inTx.PipelineTaskRunID.UUID, wrongChainID) + expErr := persistentStore.UpdateTxCallbackCompleted(testutils.Context(t), inTx.PipelineTaskRunID.UUID, wrongChainID) + assert.NoError(t, actErr) + assert.NoError(t, expErr) + + // wrong PipelineTaskRunID + wrongPipelineTaskRunID := uuid.NullUUID{UUID: uuid.New(), Valid: true} + actErr = inMemoryStore.UpdateTxCallbackCompleted(testutils.Context(t), wrongPipelineTaskRunID.UUID, chainID) + expErr = persistentStore.UpdateTxCallbackCompleted(testutils.Context(t), wrongPipelineTaskRunID.UUID, chainID) + assert.NoError(t, actErr) + assert.NoError(t, expErr) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 45e2b0da54cfba268b12df052d1307cacd2b6069 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 12 Mar 2024 00:29:51 -0400 Subject: [PATCH 39/84] implement tests for SetBroadcastBeforeBlockNum --- common/txmgr/inmemory_store.go | 2 +- common/txmgr/test_helpers.go | 9 ++ .../evm/txmgr/evm_inmemory_store_test.go | 90 +++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 9a749b8941f..0786be6efdc 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -193,7 +193,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Aband // SetBroadcastBeforeBlockNum sets the broadcast_before_block_num for a given chain ID func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SetBroadcastBeforeBlockNum(ctx context.Context, blockNum int64, chainID CHAIN_ID) error { if ms.chainID.String() != chainID.String() { - return fmt.Errorf("set_broadcast_before_block_num: %w", ErrInvalidChainID) + return nil } // Persist to persistent storage diff --git a/common/txmgr/test_helpers.go b/common/txmgr/test_helpers.go index fa9af9a506a..a0008ba1d3a 100644 --- a/common/txmgr/test_helpers.go +++ b/common/txmgr/test_helpers.go @@ -3,6 +3,7 @@ package txmgr import ( "context" "fmt" + "sort" "time" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" @@ -93,6 +94,14 @@ func (b *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) XXXTes for _, as := range b.addressStates { txs = append(txs, as.findTxs(txStates, filter, txIDs...)...) } + // sort by created_at asc and then by id asc + sort.Slice(txs, func(i, j int) bool { + if txs[i].CreatedAt.Equal(txs[j].CreatedAt) { + return txs[i].ID < txs[j].ID + } + + return txs[i].CreatedAt.Before(txs[j].CreatedAt) + }) return txs } diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a102ee1c996..3f6e4f31e3b 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,14 +1,104 @@ package txmgr_test import ( + "context" + "math/big" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" + + evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" + evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_SetBroadcastBeforeBlockNum(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("saves block num to unconfirmed evm.tx_attempts without one", func(t *testing.T) { + // Insert a transaction into persistent store + inTx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, persistentStore, 1, fromAddress) + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + headNum := int64(9000) + err := inMemoryStore.SetBroadcastBeforeBlockNum(testutils.Context(t), headNum, chainID) + require.NoError(t, err) + + expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + require.NoError(t, err) + require.Equal(t, 1, len(expTx.TxAttempts)) + assert.Equal(t, headNum, *expTx.TxAttempts[0].BroadcastBeforeBlockNum) + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.Equal(t, 1, len(actTxs)) + actTx := actTxs[0] + assertTxEqual(t, expTx, actTx) + + // wrong chain ID + wrongChainID := big.NewInt(123) + actErr := inMemoryStore.SetBroadcastBeforeBlockNum(testutils.Context(t), headNum, wrongChainID) + expErr := persistentStore.SetBroadcastBeforeBlockNum(testutils.Context(t), headNum, wrongChainID) + assert.NoError(t, actErr) + assert.NoError(t, expErr) + }) + + t.Run("does not change evm.tx_attempts that already have BroadcastBeforeBlockNum set", func(t *testing.T) { + n := int64(42) + // Insert a transaction into persistent store + inTx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, persistentStore, 11, fromAddress) + inTxAttempt := newBroadcastLegacyEthTxAttempt(t, inTx.ID, 2) + inTxAttempt.BroadcastBeforeBlockNum = &n + require.NoError(t, persistentStore.InsertTxAttempt(&inTxAttempt)) + // Insert the transaction into the in-memory store + inTx.TxAttempts = append([]evmtxmgr.TxAttempt{inTxAttempt}, inTx.TxAttempts...) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + headNum := int64(9000) + err := inMemoryStore.SetBroadcastBeforeBlockNum(testutils.Context(t), headNum, chainID) + require.NoError(t, err) + + expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + require.NoError(t, err) + require.Equal(t, 2, len(expTx.TxAttempts)) + assert.Equal(t, n, *expTx.TxAttempts[0].BroadcastBeforeBlockNum) + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.Equal(t, 1, len(actTxs)) + actTx := actTxs[0] + assertTxEqual(t, expTx, actTx) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From dff17a1695a96ef03073954cb261f37c35b4c0d6 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 00:02:04 -0400 Subject: [PATCH 40/84] implement tests for FindTxAttemptsRequiringReceiptFetch --- common/txmgr/address_state.go | 2 + common/txmgr/inmemory_store.go | 16 ++-- .../evm/txmgr/evm_inmemory_store_test.go | 94 +++++++++++++++++-- core/scripts/go.mod | 6 +- core/scripts/go.sum | 12 +-- go.mod | 6 +- go.sum | 12 +-- integration-tests/go.mod | 6 +- integration-tests/go.sum | 12 +-- integration-tests/load/go.mod | 6 +- integration-tests/load/go.sum | 12 +-- 11 files changed, 135 insertions(+), 49 deletions(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index ed327b69960..2577cbc9829 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -465,6 +465,7 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) _findT for i := 0; i < len(tx.TxAttempts); i++ { txAttempt := tx.TxAttempts[i] if txAttemptFilter(&txAttempt) { + txAttempt.Tx = *tx txAttempts = append(txAttempts, txAttempt) } } @@ -479,6 +480,7 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) _findT for i := 0; i < len(tx.TxAttempts); i++ { txAttempt := tx.TxAttempts[i] if txAttemptFilter(&txAttempt) { + txAttempt.Tx = *tx txAttempts = append(txAttempts, txAttempt) } } diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 407a792f746..053e095b3e7 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -1,11 +1,13 @@ package txmgr import ( + "cmp" "context" "encoding/json" "errors" "fmt" "math/big" + "slices" "sort" "sync" "time" @@ -319,11 +321,11 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT err error, ) { if ms.chainID.String() != chainID.String() { - return attempts, fmt.Errorf("find_tx_attempts_requiring_receipt_fetch: %w", ErrInvalidChainID) + return attempts, nil } txFilterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - return tx.TxAttempts != nil && len(tx.TxAttempts) > 0 + return len(tx.TxAttempts) > 0 } txAttemptFilterFn := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { return attempt.State != txmgrtypes.TxAttemptInsufficientFunds @@ -337,13 +339,13 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT } // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee - sort.Slice(attempts, func(i, j int) bool { - iSequence, jSequence := attempts[i].Tx.Sequence, attempts[j].Tx.Sequence - if iSequence == nil || jSequence == nil { - return false + slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { + aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence + if aSequence == nil || bSequence == nil { + return 0 } - return (*attempts[i].Tx.Sequence).Int64() < (*attempts[j].Tx.Sequence).Int64() + return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) }) // deep copy the attempts diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 1c184479a10..7e0263744f7 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -2,7 +2,6 @@ package txmgr_test import ( "context" - "fmt" "math/big" "testing" "time" @@ -319,7 +318,6 @@ func TestInMemoryStore_FindTxAttemptsConfirmedMissingReceipt(t *testing.T) { {2, 1, time.Unix(1616509500, 0)}, } for _, inTxData := range inTxDatas { - fmt.Println("DATA", inTxData.nonce, inTxData.broadcastBeforeBlockNum, inTxData.broadcastAt) // insert the transaction into the persistent store inTx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( t, persistentStore, inTxData.nonce, inTxData.broadcastBeforeBlockNum, @@ -361,6 +359,81 @@ func TestInMemoryStore_FindTxAttemptsConfirmedMissingReceipt(t *testing.T) { } } +func TestInMemoryStore_FindTxAttemptsRequiringReceiptFetch(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + // initialize transactions + inTxDatas := []struct { + nonce int64 + broadcastBeforeBlockNum int64 + broadcastAt time.Time + }{ + {0, 1, time.Unix(1616509300, 0)}, + {1, 1, time.Unix(1616509400, 0)}, + {2, 1, time.Unix(1616509500, 0)}, + } + for _, inTxData := range inTxDatas { + // insert the transaction into the persistent store + inTx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt( + t, persistentStore, inTxData.nonce, inTxData.broadcastBeforeBlockNum, + inTxData.broadcastAt, fromAddress, + ) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + } + + tcs := []struct { + name string + inChainID *big.Int + + expTxAttemptsCount int + hasError bool + }{ + {"finds tx attempts requiring receipt fetch", chainID, 3, false}, + {"wrong chain", big.NewInt(999), 0, false}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + ctx := testutils.Context(t) + expTxAttempts, expErr := persistentStore.FindTxAttemptsRequiringReceiptFetch(ctx, tc.inChainID) + actTxAttempts, actErr := inMemoryStore.FindTxAttemptsRequiringReceiptFetch(ctx, tc.inChainID) + if tc.hasError { + require.NotNil(t, actErr) + require.NotNil(t, expErr) + } else { + require.NoError(t, actErr) + require.NoError(t, expErr) + require.Equal(t, tc.expTxAttemptsCount, len(expTxAttempts)) + require.Equal(t, tc.expTxAttemptsCount, len(actTxAttempts)) + for i := 0; i < len(expTxAttempts); i++ { + assertTxAttemptEqual(t, expTxAttempts[i], actTxAttempts[i]) + } + } + }) + } +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) @@ -372,8 +445,18 @@ func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.Value, act.Value) assert.Equal(t, exp.FeeLimit, act.FeeLimit) assert.Equal(t, exp.Error, act.Error) - assert.Equal(t, exp.BroadcastAt, act.BroadcastAt) - assert.Equal(t, exp.InitialBroadcastAt, act.InitialBroadcastAt) + if exp.BroadcastAt != nil { + require.NotNil(t, act.BroadcastAt) + assert.Equal(t, exp.BroadcastAt.Unix(), act.BroadcastAt.Unix()) + } else { + assert.Equal(t, exp.BroadcastAt, act.BroadcastAt) + } + if exp.InitialBroadcastAt != nil { + require.NotNil(t, act.InitialBroadcastAt) + assert.Equal(t, exp.InitialBroadcastAt.Unix(), act.InitialBroadcastAt.Unix()) + } else { + assert.Equal(t, exp.InitialBroadcastAt, act.InitialBroadcastAt) + } assert.Equal(t, exp.CreatedAt, act.CreatedAt) assert.Equal(t, exp.State, act.State) assert.Equal(t, exp.Meta, act.Meta) @@ -385,7 +468,7 @@ func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.SignalCallback, act.SignalCallback) assert.Equal(t, exp.CallbackCompleted, act.CallbackCompleted) - require.Len(t, exp.TxAttempts, len(act.TxAttempts)) + require.Equal(t, len(exp.TxAttempts), len(act.TxAttempts)) for i := 0; i < len(exp.TxAttempts); i++ { assertTxAttemptEqual(t, exp.TxAttempts[i], act.TxAttempts[i]) } @@ -394,7 +477,6 @@ func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { func assertTxAttemptEqual(t *testing.T, exp, act evmtxmgr.TxAttempt) { assert.Equal(t, exp.ID, act.ID) assert.Equal(t, exp.TxID, act.TxID) - assert.Equal(t, exp.Tx, act.Tx) assert.Equal(t, exp.TxFee, act.TxFee) assert.Equal(t, exp.ChainSpecificFeeLimit, act.ChainSpecificFeeLimit) assert.Equal(t, exp.SignedRawTx, act.SignedRawTx) diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 38444af9e82..f7217d33039 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -157,8 +157,8 @@ require ( github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/sessions v1.2.2 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/grafana/pyroscope-go v1.0.4 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.4 // indirect + github.com/grafana/pyroscope-go v1.1.1 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // indirect github.com/graph-gophers/dataloader v5.0.0+incompatible // indirect github.com/graph-gophers/graphql-go v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -195,7 +195,7 @@ require ( github.com/jmhodges/levigo v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.3 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 4be489ecedc..98a0b32fe68 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -658,10 +658,10 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grafana/pyroscope-go v1.0.4 h1:oyQX0BOkL+iARXzHuCdIF5TQ7/sRSel1YFViMHC7Bm0= -github.com/grafana/pyroscope-go v1.0.4/go.mod h1:0d7ftwSMBV/Awm7CCiYmHQEG8Y44Ma3YSjt+nWcWztY= -github.com/grafana/pyroscope-go/godeltaprof v0.1.4 h1:mDsJ3ngul7UfrHibGQpV66PbZ3q1T8glz/tK3bQKKEk= -github.com/grafana/pyroscope-go/godeltaprof v0.1.4/go.mod h1:1HSPtjU8vLG0jE9JrTdzjgFqdJ/VgN7fvxBNq3luJko= +github.com/grafana/pyroscope-go v1.1.1 h1:PQoUU9oWtO3ve/fgIiklYuGilvsm8qaGhlY4Vw6MAcQ= +github.com/grafana/pyroscope-go v1.1.1/go.mod h1:Mw26jU7jsL/KStNSGGuuVYdUq7Qghem5P8aXYXSXG88= +github.com/grafana/pyroscope-go/godeltaprof v0.1.6 h1:nEdZ8louGAplSvIJi1HVp7kWvFvdiiYg3COLlTwJiFo= +github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4= github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= @@ -868,8 +868,8 @@ github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= +github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= diff --git a/go.mod b/go.mod index cf20ecf9ca7..3a6c9ed911a 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/gorilla/securecookie v1.1.2 github.com/gorilla/sessions v1.2.2 github.com/gorilla/websocket v1.5.1 - github.com/grafana/pyroscope-go v1.0.4 + github.com/grafana/pyroscope-go v1.1.1 github.com/graph-gophers/dataloader v5.0.0+incompatible github.com/graph-gophers/graphql-go v1.3.0 github.com/hashicorp/consul/sdk v0.14.1 @@ -210,7 +210,7 @@ require ( github.com/google/go-tpm v0.9.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/gorilla/context v1.1.1 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.4 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.3 // indirect @@ -240,7 +240,7 @@ require ( github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.3 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect diff --git a/go.sum b/go.sum index 733b0e4b2d8..a062bef3dae 100644 --- a/go.sum +++ b/go.sum @@ -649,10 +649,10 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= -github.com/grafana/pyroscope-go v1.0.4 h1:oyQX0BOkL+iARXzHuCdIF5TQ7/sRSel1YFViMHC7Bm0= -github.com/grafana/pyroscope-go v1.0.4/go.mod h1:0d7ftwSMBV/Awm7CCiYmHQEG8Y44Ma3YSjt+nWcWztY= -github.com/grafana/pyroscope-go/godeltaprof v0.1.4 h1:mDsJ3ngul7UfrHibGQpV66PbZ3q1T8glz/tK3bQKKEk= -github.com/grafana/pyroscope-go/godeltaprof v0.1.4/go.mod h1:1HSPtjU8vLG0jE9JrTdzjgFqdJ/VgN7fvxBNq3luJko= +github.com/grafana/pyroscope-go v1.1.1 h1:PQoUU9oWtO3ve/fgIiklYuGilvsm8qaGhlY4Vw6MAcQ= +github.com/grafana/pyroscope-go v1.1.1/go.mod h1:Mw26jU7jsL/KStNSGGuuVYdUq7Qghem5P8aXYXSXG88= +github.com/grafana/pyroscope-go/godeltaprof v0.1.6 h1:nEdZ8louGAplSvIJi1HVp7kWvFvdiiYg3COLlTwJiFo= +github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= github.com/graph-gophers/dataloader v5.0.0+incompatible/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4= github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= @@ -861,8 +861,8 @@ github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= +github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 7ca83bd6fc3..f3d6b145218 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -235,8 +235,8 @@ require ( github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586 // indirect github.com/grafana/loki v1.6.2-0.20231215164305-b51b7d7b5503 // indirect github.com/grafana/loki/pkg/push v0.0.0-20231201111602-11ef833ed3e4 // indirect - github.com/grafana/pyroscope-go v1.0.4 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.4 // indirect + github.com/grafana/pyroscope-go v1.1.1 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // indirect github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -289,7 +289,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.3 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index c4a68c60c40..e0cf675dd01 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -859,10 +859,10 @@ github.com/grafana/loki v1.6.2-0.20231215164305-b51b7d7b5503 h1:gdrsYbmk8822v6qv github.com/grafana/loki v1.6.2-0.20231215164305-b51b7d7b5503/go.mod h1:d8seWXCEXkL42mhuIJYcGi6DxfehzoIpLrMQWJojvOo= github.com/grafana/loki/pkg/push v0.0.0-20231201111602-11ef833ed3e4 h1:wQ0FnSeebhJIBkgYOD06Mxk9HV2KhtEG0hp/7R+5RUQ= github.com/grafana/loki/pkg/push v0.0.0-20231201111602-11ef833ed3e4/go.mod h1:f3JSoxBTPXX5ec4FxxeC19nTBSxoTz+cBgS3cYLMcr0= -github.com/grafana/pyroscope-go v1.0.4 h1:oyQX0BOkL+iARXzHuCdIF5TQ7/sRSel1YFViMHC7Bm0= -github.com/grafana/pyroscope-go v1.0.4/go.mod h1:0d7ftwSMBV/Awm7CCiYmHQEG8Y44Ma3YSjt+nWcWztY= -github.com/grafana/pyroscope-go/godeltaprof v0.1.4 h1:mDsJ3ngul7UfrHibGQpV66PbZ3q1T8glz/tK3bQKKEk= -github.com/grafana/pyroscope-go/godeltaprof v0.1.4/go.mod h1:1HSPtjU8vLG0jE9JrTdzjgFqdJ/VgN7fvxBNq3luJko= +github.com/grafana/pyroscope-go v1.1.1 h1:PQoUU9oWtO3ve/fgIiklYuGilvsm8qaGhlY4Vw6MAcQ= +github.com/grafana/pyroscope-go v1.1.1/go.mod h1:Mw26jU7jsL/KStNSGGuuVYdUq7Qghem5P8aXYXSXG88= +github.com/grafana/pyroscope-go/godeltaprof v0.1.6 h1:nEdZ8louGAplSvIJi1HVp7kWvFvdiiYg3COLlTwJiFo= +github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww= github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= @@ -1109,8 +1109,8 @@ github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= +github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 0e7bb2efa02..193906aafea 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -217,8 +217,8 @@ require ( github.com/grafana/gomemcache v0.0.0-20231023152154-6947259a0586 // indirect github.com/grafana/loki v1.6.2-0.20231215164305-b51b7d7b5503 // indirect github.com/grafana/loki/pkg/push v0.0.0-20231201111602-11ef833ed3e4 // indirect - github.com/grafana/pyroscope-go v1.0.4 // indirect - github.com/grafana/pyroscope-go/godeltaprof v0.1.4 // indirect + github.com/grafana/pyroscope-go v1.1.1 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // indirect github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect @@ -272,7 +272,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/compress v1.17.3 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index 998d0b22a66..1409b333583 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -850,10 +850,10 @@ github.com/grafana/loki v1.6.2-0.20231215164305-b51b7d7b5503 h1:gdrsYbmk8822v6qv github.com/grafana/loki v1.6.2-0.20231215164305-b51b7d7b5503/go.mod h1:d8seWXCEXkL42mhuIJYcGi6DxfehzoIpLrMQWJojvOo= github.com/grafana/loki/pkg/push v0.0.0-20231201111602-11ef833ed3e4 h1:wQ0FnSeebhJIBkgYOD06Mxk9HV2KhtEG0hp/7R+5RUQ= github.com/grafana/loki/pkg/push v0.0.0-20231201111602-11ef833ed3e4/go.mod h1:f3JSoxBTPXX5ec4FxxeC19nTBSxoTz+cBgS3cYLMcr0= -github.com/grafana/pyroscope-go v1.0.4 h1:oyQX0BOkL+iARXzHuCdIF5TQ7/sRSel1YFViMHC7Bm0= -github.com/grafana/pyroscope-go v1.0.4/go.mod h1:0d7ftwSMBV/Awm7CCiYmHQEG8Y44Ma3YSjt+nWcWztY= -github.com/grafana/pyroscope-go/godeltaprof v0.1.4 h1:mDsJ3ngul7UfrHibGQpV66PbZ3q1T8glz/tK3bQKKEk= -github.com/grafana/pyroscope-go/godeltaprof v0.1.4/go.mod h1:1HSPtjU8vLG0jE9JrTdzjgFqdJ/VgN7fvxBNq3luJko= +github.com/grafana/pyroscope-go v1.1.1 h1:PQoUU9oWtO3ve/fgIiklYuGilvsm8qaGhlY4Vw6MAcQ= +github.com/grafana/pyroscope-go v1.1.1/go.mod h1:Mw26jU7jsL/KStNSGGuuVYdUq7Qghem5P8aXYXSXG88= +github.com/grafana/pyroscope-go/godeltaprof v0.1.6 h1:nEdZ8louGAplSvIJi1HVp7kWvFvdiiYg3COLlTwJiFo= +github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww= github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/graph-gophers/dataloader v5.0.0+incompatible h1:R+yjsbrNq1Mo3aPG+Z/EKYrXrXXUNJHOgbRt+U6jOug= @@ -1098,8 +1098,8 @@ github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= +github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= From ed9ad22cab0cecd16d2fad1afa8f9cb59862a579 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 17:47:49 -0400 Subject: [PATCH 41/84] implement tests for GetInProgressTxAttempts --- .../evm/txmgr/evm_inmemory_store_test.go | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 7e0263744f7..030672c0b29 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -12,6 +12,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" + txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" @@ -434,6 +435,56 @@ func TestInMemoryStore_FindTxAttemptsRequiringReceiptFetch(t *testing.T) { } } +func TestInMemoryStore_GetInProgressTxAttempts(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("gets 0 in progress transaction", func(t *testing.T) { + ctx := testutils.Context(t) + expTxAttempts, expErr := persistentStore.GetInProgressTxAttempts(ctx, fromAddress, chainID) + actTxAttempts, actErr := inMemoryStore.GetInProgressTxAttempts(ctx, fromAddress, chainID) + require.NoError(t, actErr) + require.NoError(t, expErr) + assert.Equal(t, len(expTxAttempts), len(actTxAttempts)) + }) + + t.Run("gets 1 in progress transaction", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := mustInsertUnconfirmedEthTxWithAttemptState(t, persistentStore, int64(7), fromAddress, txmgrtypes.TxAttemptInProgress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + expTxAttempts, expErr := persistentStore.GetInProgressTxAttempts(ctx, fromAddress, chainID) + actTxAttempts, actErr := inMemoryStore.GetInProgressTxAttempts(ctx, fromAddress, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + require.Equal(t, len(expTxAttempts), len(actTxAttempts)) + for i := 0; i < len(expTxAttempts); i++ { + assertTxAttemptEqual(t, expTxAttempts[i], actTxAttempts[i]) + } + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From dca5e1ff7bbb1b91395f5487d49c0b38947d25ec Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 18:01:39 -0400 Subject: [PATCH 42/84] implement tests for HasInProgressTransaction --- .../evm/txmgr/evm_inmemory_store_test.go | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 030672c0b29..451a2c7cc50 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -485,6 +485,53 @@ func TestInMemoryStore_GetInProgressTxAttempts(t *testing.T) { }) } +func TestInMemoryStore_HasInProgressTransaction(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("no in progress transaction", func(t *testing.T) { + ctx := testutils.Context(t) + expExists, expErr := persistentStore.HasInProgressTransaction(ctx, fromAddress, chainID) + actExists, actErr := inMemoryStore.HasInProgressTransaction(ctx, fromAddress, chainID) + require.NoError(t, actErr) + require.NoError(t, expErr) + assert.Equal(t, expExists, actExists) + }) + + t.Run("has an in progress transaction", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 7, fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + expExists, expErr := persistentStore.HasInProgressTransaction(ctx, fromAddress, chainID) + actExists, actErr := inMemoryStore.HasInProgressTransaction(ctx, fromAddress, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + require.Equal(t, expExists, actExists) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 1df21a2cd23d3cf6e7b559d250c8e711aab4e2b2 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 18:10:42 -0400 Subject: [PATCH 43/84] implement tests for GetTxByID --- common/txmgr/inmemory_store.go | 2 +- .../evm/txmgr/evm_inmemory_store_test.go | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 053e095b3e7..b2e02acd7b8 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -941,7 +941,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetTx } } - return nil, fmt.Errorf("failed to get tx with id: %v", id) + return nil, nil } func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) HasInProgressTransaction(_ context.Context, account ADDR, chainID CHAIN_ID) (bool, error) { diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 451a2c7cc50..a20ec87d2a1 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -532,6 +532,56 @@ func TestInMemoryStore_HasInProgressTransaction(t *testing.T) { }) } +func TestInMemoryStore_GetTxByID(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("no transaction", func(t *testing.T) { + ctx := testutils.Context(t) + expTx, expErr := persistentStore.GetTxByID(ctx, 0) + actTx, actErr := inMemoryStore.GetTxByID(ctx, 0) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Nil(t, expTx) + assert.Nil(t, actTx) + }) + + t.Run("successfully get transaction by ID", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 7, fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + expTx, expErr := persistentStore.GetTxByID(ctx, inTx.ID) + actTx, actErr := inMemoryStore.GetTxByID(ctx, inTx.ID) + require.NoError(t, expErr) + require.NoError(t, actErr) + require.NotNil(t, expTx) + require.NotNil(t, actTx) + assertTxEqual(t, *expTx, *actTx) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From e3438b4ce6ecd23d9c5760f0a9ae81a0084ab9c3 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 18:18:41 -0400 Subject: [PATCH 44/84] implement tests for FindTxWithSequence --- common/txmgr/inmemory_store.go | 2 +- .../evm/txmgr/evm_inmemory_store_test.go | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index b2e02acd7b8..309eceacc27 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -708,7 +708,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT defer ms.addressStatesLock.RUnlock() as, ok := ms.addressStates[fromAddress] if !ok { - return nil, fmt.Errorf("find_tx_with_sequence: %w", ErrAddressNotFound) + return nil, nil } filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a20ec87d2a1..7065fad50dc 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -582,6 +582,72 @@ func TestInMemoryStore_GetTxByID(t *testing.T) { }) } +func TestInMemoryStore_FindTxWithSequence(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("no results", func(t *testing.T) { + ctx := testutils.Context(t) + expTx, expErr := persistentStore.FindTxWithSequence(ctx, fromAddress, evmtypes.Nonce(666)) + actTx, actErr := inMemoryStore.FindTxWithSequence(ctx, fromAddress, evmtypes.Nonce(666)) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Nil(t, expTx) + assert.Nil(t, actTx) + }) + + t.Run("successfully get transaction by ID", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 666, 1, fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + expTx, expErr := persistentStore.FindTxWithSequence(ctx, fromAddress, evmtypes.Nonce(666)) + actTx, actErr := inMemoryStore.FindTxWithSequence(ctx, fromAddress, evmtypes.Nonce(666)) + require.NoError(t, expErr) + require.NoError(t, actErr) + require.NotNil(t, expTx) + require.NotNil(t, actTx) + assertTxEqual(t, *expTx, *actTx) + }) + + t.Run("incorrect from address", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 777, 7, fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + wrongFromAddress := common.Address{} + ctx := testutils.Context(t) + expTx, expErr := persistentStore.FindTxWithSequence(ctx, wrongFromAddress, evmtypes.Nonce(777)) + actTx, actErr := inMemoryStore.FindTxWithSequence(ctx, wrongFromAddress, evmtypes.Nonce(777)) + require.NoError(t, expErr) + require.NoError(t, actErr) + require.Nil(t, expTx) + require.Nil(t, actTx) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From f017ea182107635645b3711f8daf84c20aaaa135 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 18:33:26 -0400 Subject: [PATCH 45/84] implement tests for CountTransactionsByState --- common/txmgr/inmemory_store.go | 2 +- .../evm/txmgr/evm_inmemory_store_test.go | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 309eceacc27..af34dbe71ef 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -607,7 +607,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) ReapT } func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountTransactionsByState(_ context.Context, state txmgrtypes.TxState, chainID CHAIN_ID) (uint32, error) { if ms.chainID.String() != chainID.String() { - return 0, fmt.Errorf("count_transactions_by_state: %w", ErrInvalidChainID) + return 0, nil } var total int diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 7065fad50dc..ef9d550fd07 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -648,6 +648,63 @@ func TestInMemoryStore_FindTxWithSequence(t *testing.T) { }) } +func TestInMemoryStore_CountTransactionsByState(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("no results", func(t *testing.T) { + ctx := testutils.Context(t) + expCount, expErr := persistentStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, chainID) + actCount, actErr := inMemoryStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, expCount, actCount) + }) + t.Run("wrong chain id", func(t *testing.T) { + wrongChainID := big.NewInt(999) + ctx := testutils.Context(t) + expCount, expErr := persistentStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, wrongChainID) + actCount, actErr := inMemoryStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, wrongChainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, expCount, actCount) + }) + t.Run("3 unconfirmed transactions", func(t *testing.T) { + for i := int64(0); i < 3; i++ { + // insert the transaction into the persistent store + inTx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, persistentStore, i, fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + } + + ctx := testutils.Context(t) + expCount, expErr := persistentStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, chainID) + actCount, actErr := inMemoryStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, expCount, actCount) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 81b635952612bd31ccfcb06bcd65bf6d7f79d523 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 22:05:28 -0400 Subject: [PATCH 46/84] implement tests for FindTxsRequiringResubmissionDueToInsufficientFunds --- common/txmgr/inmemory_store.go | 15 ++- .../evm/txmgr/evm_inmemory_store_test.go | 93 +++++++++++++++++++ 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index af34dbe71ef..139b4bcb87d 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -626,18 +626,18 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Delet func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxsRequiringResubmissionDueToInsufficientFunds(_ context.Context, address ADDR, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("find_txs_requiring_resubmission_due_to_insufficient_funds: %w", ErrInvalidChainID) + return nil, nil } ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() as, ok := ms.addressStates[address] if !ok { - return nil, fmt.Errorf("find_txs_requiring_resubmission_due_to_insufficient_funds: %w", ErrAddressNotFound) + return nil, nil } filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + if len(tx.TxAttempts) == 0 { return false } for _, attempt := range tx.TxAttempts { @@ -650,8 +650,13 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT states := []txmgrtypes.TxState{TxUnconfirmed} txs := as.findTxs(states, filter) // sort by sequence ASC - sort.Slice(txs, func(i, j int) bool { - return (*txs[i].Sequence).Int64() < (*txs[j].Sequence).Int64() + slices.SortFunc(txs, func(a, b txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { + aSequence, bSequence := a.Sequence, b.Sequence + if aSequence == nil || bSequence == nil { + return 0 + } + + return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) }) etxs := make([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], len(txs)) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index ef9d550fd07..8d1047cdf0b 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -14,6 +14,7 @@ import ( commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -705,6 +706,98 @@ func TestInMemoryStore_CountTransactionsByState(t *testing.T) { }) } +func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + _, otherAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("no results", func(t *testing.T) { + ctx := testutils.Context(t) + expTxs, expErr := persistentStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, chainID) + actTxs, actErr := inMemoryStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, len(expTxs), len(actTxs)) + }) + + // Insert order is mixed up to test sorting + // insert the transaction into the persistent store + inTx_2 := mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, persistentStore, 1, fromAddress) + inTx_3 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, persistentStore, 2, fromAddress) + attempt3_2 := cltest.NewLegacyEthTxAttempt(t, inTx_3.ID) + attempt3_2.State = txmgrtypes.TxAttemptInsufficientFunds + attempt3_2.TxFee.Legacy = assets.NewWeiI(100) + require.NoError(t, persistentStore.InsertTxAttempt(&attempt3_2)) + inTx_1 := mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, persistentStore, 0, fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_2)) + inTx_3.TxAttempts = append([]evmtxmgr.TxAttempt{attempt3_2}, inTx_3.TxAttempts...) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_3)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_1)) + + // These should never be returned + // insert the transaction into the persistent store + otx_1 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, persistentStore, 3, fromAddress) + otx_2 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 4, 100, fromAddress) + otx_3 := mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, persistentStore, 0, otherAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &otx_1)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &otx_2)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(otherAddress, &otx_3)) + + t.Run("return all eth_txes with at least one attempt that is in insufficient_eth state", func(t *testing.T) { + ctx := testutils.Context(t) + expTxs, expErr := persistentStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, chainID) + actTxs, actErr := inMemoryStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + + assert.Equal(t, len(expTxs), len(actTxs)) + for i := 0; i < len(expTxs); i++ { + assertTxEqual(t, *expTxs[i], *actTxs[i]) + } + }) + + t.Run("does not return txes with different chainID", func(t *testing.T) { + wrongChainID := big.NewInt(999) + ctx := testutils.Context(t) + expTxs, expErr := persistentStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, wrongChainID) + actTxs, actErr := inMemoryStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, wrongChainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, len(expTxs), len(actTxs)) + }) + + t.Run("does not return txes with different fromAddress", func(t *testing.T) { + anotherFromAddress := common.Address{} + ctx := testutils.Context(t) + expTxs, expErr := persistentStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, anotherFromAddress, chainID) + actTxs, actErr := inMemoryStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, anotherFromAddress, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, len(expTxs), len(actTxs)) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 158972f8e11d9343e871b5a42c656aa4c08ba343 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 22:12:08 -0400 Subject: [PATCH 47/84] implement tests for GetNonFatalTransactions --- .../evm/txmgr/evm_inmemory_store_test.go | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 8d1047cdf0b..f9da2618663 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -798,6 +798,59 @@ func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testi }) } +func TestInMemoryStore_GetNonFatalTransactions(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("no results", func(t *testing.T) { + ctx := testutils.Context(t) + expTxs, expErr := persistentStore.GetNonFatalTransactions(ctx, chainID) + actTxs, actErr := inMemoryStore.GetNonFatalTransactions(ctx, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, len(expTxs), len(actTxs)) + }) + + t.Run("get in progress, unstarted, and unconfirmed transactions", func(t *testing.T) { + // insert the transaction into the persistent store + inTx_0 := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 123, fromAddress) + inTx_1 := mustCreateUnstartedGeneratedTx(t, persistentStore, fromAddress, chainID) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_1)) + + ctx := testutils.Context(t) + expTxs, expErr := persistentStore.GetNonFatalTransactions(ctx, chainID) + actTxs, actErr := inMemoryStore.GetNonFatalTransactions(ctx, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + require.Equal(t, len(expTxs), len(actTxs)) + + for i := 0; i < len(expTxs); i++ { + assertTxEqual(t, *expTxs[i], *actTxs[i]) + } + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 431776bdb7f9454288a59e4752840f65d8ae2918 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 22:15:31 -0400 Subject: [PATCH 48/84] add test for wrong chain id and clean up --- common/txmgr/inmemory_store.go | 15 +++------------ core/chains/evm/txmgr/evm_inmemory_store_test.go | 10 ++++++++++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 139b4bcb87d..07beab076f9 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -902,28 +902,19 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetIn func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetNonFatalTransactions(ctx context.Context, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("get_non_fatal_transactions: %w", ErrInvalidChainID) + return nil, nil } filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { return tx.State != TxFatalError } - txsLock := sync.Mutex{} txs := []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} - wg := sync.WaitGroup{} ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() for _, as := range ms.addressStates { - wg.Add(1) - go func(as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { - etxs := as.findTxs(nil, filter) - txsLock.Lock() - txs = append(txs, etxs...) - txsLock.Unlock() - wg.Done() - }(as) + etxs := as.findTxs(nil, filter) + txs = append(txs, etxs...) } - wg.Wait() etxs := make([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], len(txs)) for i, tx := range txs { diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index f9da2618663..a2fd1650207 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -849,6 +849,16 @@ func TestInMemoryStore_GetNonFatalTransactions(t *testing.T) { assertTxEqual(t, *expTxs[i], *actTxs[i]) } }) + + t.Run("wrong chain ID", func(t *testing.T) { + wrongChainID := big.NewInt(999) + ctx := testutils.Context(t) + expTxs, expErr := persistentStore.GetNonFatalTransactions(ctx, wrongChainID) + actTxs, actErr := inMemoryStore.GetNonFatalTransactions(ctx, wrongChainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, len(expTxs), len(actTxs)) + }) } // assertTxEqual asserts that two transactions are equal From 676917bfeaf0f552ca0500f4d0b03992f8cb9abd Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 22:57:55 -0400 Subject: [PATCH 49/84] implement FindTransactionsConfirmedInBlockRange --- common/txmgr/inmemory_store.go | 26 +++---- .../evm/txmgr/evm_inmemory_store_test.go | 68 +++++++++++++++++++ 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 07beab076f9..4573cbaafd6 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -734,11 +734,11 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTransactionsConfirmedInBlockRange(_ context.Context, highBlockNumber, lowBlockNumber int64, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("find_transactions_confirmed_in_block_range: %w", ErrInvalidChainID) + return nil, nil } filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + if len(tx.TxAttempts) == 0 { return false } for _, attempt := range tx.TxAttempts { @@ -760,25 +760,21 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT return false } states := []txmgrtypes.TxState{TxConfirmed, TxConfirmedMissingReceipt} - txsLock := sync.Mutex{} txs := []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} - wg := sync.WaitGroup{} ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() for _, as := range ms.addressStates { - wg.Add(1) - go func(as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { - ts := as.findTxs(states, filter) - txsLock.Lock() - txs = append(txs, ts...) - txsLock.Unlock() - wg.Done() - }(as) + ts := as.findTxs(states, filter) + txs = append(txs, ts...) } - wg.Wait() // sort by sequence ASC - sort.Slice(txs, func(i, j int) bool { - return (*txs[i].Sequence).Int64() < (*txs[j].Sequence).Int64() + slices.SortFunc(txs, func(a, b txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { + aSequence, bSequence := a.Sequence, b.Sequence + if aSequence == nil || bSequence == nil { + return 0 + } + + return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) }) etxs := make([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], len(txs)) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a2fd1650207..9ca66f9a4a8 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -18,6 +18,7 @@ import ( evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" @@ -861,6 +862,73 @@ func TestInMemoryStore_GetNonFatalTransactions(t *testing.T) { }) } +func TestInMemoryStore_FindTransactionsConfirmedInBlockRange(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("no results", func(t *testing.T) { + ctx := testutils.Context(t) + expTxs, expErr := persistentStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, chainID) + actTxs, actErr := inMemoryStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, len(expTxs), len(actTxs)) + }) + + t.Run("find all transactions confirmed in range", func(t *testing.T) { + // insert the transaction into the persistent store + inTx_0 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 700, 8, fromAddress) + rec_0 := mustInsertEthReceipt(t, persistentStore, 8, utils.NewHash(), inTx_0.TxAttempts[0].Hash) + inTx_1 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 777, 9, fromAddress) + rec_1 := mustInsertEthReceipt(t, persistentStore, 9, utils.NewHash(), inTx_1.TxAttempts[0].Hash) + // insert the transaction into the in-memory store + inTx_0.TxAttempts[0].Receipts = append(inTx_0.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec_0)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) + inTx_1.TxAttempts[0].Receipts = append(inTx_1.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec_1)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_1)) + + ctx := testutils.Context(t) + expTxs, expErr := persistentStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, chainID) + actTxs, actErr := inMemoryStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + require.Equal(t, len(expTxs), len(actTxs)) + for i := 0; i < len(expTxs); i++ { + assertTxEqual(t, *expTxs[i], *actTxs[i]) + } + }) + + t.Run("wrong chain ID", func(t *testing.T) { + wrongChainID := big.NewInt(999) + ctx := testutils.Context(t) + expTxs, expErr := persistentStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, wrongChainID) + actTxs, actErr := inMemoryStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, wrongChainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, len(expTxs), len(actTxs)) + }) + +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 164f48d562283161cb3b357b55c24481990243a3 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 23:16:49 -0400 Subject: [PATCH 50/84] implement tests for FindEarliestUnconfirmedBroadcastTime --- common/txmgr/inmemory_store.go | 23 ++++----- .../evm/txmgr/evm_inmemory_store_test.go | 49 +++++++++++++++++++ 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 4573cbaafd6..baa82fe5eaf 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -786,36 +786,33 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT } func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindEarliestUnconfirmedBroadcastTime(ctx context.Context, chainID CHAIN_ID) (null.Time, error) { if ms.chainID.String() != chainID.String() { - return null.Time{}, fmt.Errorf("find_earliest_unconfirmed_broadcast_time: %w", ErrInvalidChainID) + return null.Time{}, nil } filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { return tx.InitialBroadcastAt != nil } states := []txmgrtypes.TxState{TxUnconfirmed} - txsLock := sync.Mutex{} txs := []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} - wg := sync.WaitGroup{} ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() for _, as := range ms.addressStates { - wg.Add(1) - go func(as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { - etxs := as.findTxs(states, filter) - txsLock.Lock() - txs = append(txs, etxs...) - txsLock.Unlock() - wg.Done() - }(as) + etxs := as.findTxs(states, filter) + txs = append(txs, etxs...) } - wg.Wait() var minInitialBroadcastAt time.Time for _, tx := range txs { - if tx.InitialBroadcastAt.Before(minInitialBroadcastAt) { + if tx.InitialBroadcastAt == nil { + continue + } + if minInitialBroadcastAt.IsZero() || tx.InitialBroadcastAt.Before(minInitialBroadcastAt) { minInitialBroadcastAt = *tx.InitialBroadcastAt } } + if minInitialBroadcastAt.IsZero() { + return null.Time{}, nil + } return null.TimeFrom(minInitialBroadcastAt), nil } diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 9ca66f9a4a8..a034e279771 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -926,6 +926,55 @@ func TestInMemoryStore_FindTransactionsConfirmedInBlockRange(t *testing.T) { require.NoError(t, actErr) assert.Equal(t, len(expTxs), len(actTxs)) }) +} + +func TestInMemoryStore_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("no results", func(t *testing.T) { + ctx := testutils.Context(t) + expBroadcastAt, expErr := persistentStore.FindEarliestUnconfirmedBroadcastTime(ctx, chainID) + actBroadcastAt, actErr := inMemoryStore.FindEarliestUnconfirmedBroadcastTime(ctx, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.False(t, expBroadcastAt.Valid) + assert.False(t, actBroadcastAt.Valid) + }) + t.Run("find broadcast at time", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := cltest.MustInsertUnconfirmedEthTx(t, persistentStore, 123, fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + expBroadcastAt, expErr := persistentStore.FindEarliestUnconfirmedBroadcastTime(ctx, chainID) + actBroadcastAt, actErr := inMemoryStore.FindEarliestUnconfirmedBroadcastTime(ctx, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + require.True(t, expBroadcastAt.Valid) + require.True(t, actBroadcastAt.Valid) + assert.Equal(t, expBroadcastAt.Time.Unix(), actBroadcastAt.Time.Unix()) + }) } From fe601b2d75377ec531752dcce72597a931c3ae5b Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 19 Mar 2024 23:38:36 -0400 Subject: [PATCH 51/84] implements tests for FindEarliestUnconfirmedTxAttemptBlock --- common/txmgr/inmemory_store.go | 22 ++---- .../evm/txmgr/evm_inmemory_store_test.go | 77 +++++++++++++++++++ 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index baa82fe5eaf..87cb1ad8a9a 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -819,11 +819,11 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindE func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context, chainID CHAIN_ID) (null.Int, error) { if ms.chainID.String() != chainID.String() { - return null.Int{}, fmt.Errorf("find_earliest_unconfirmed_broadcast_time: %w", ErrInvalidChainID) + return null.Int{}, nil } filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + if len(tx.TxAttempts) == 0 { return false } @@ -836,29 +836,23 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindE return false } states := []txmgrtypes.TxState{TxUnconfirmed} - txsLock := sync.Mutex{} txs := []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} - wg := sync.WaitGroup{} ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() for _, as := range ms.addressStates { - wg.Add(1) - go func(as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { - etxs := as.findTxs(states, filter) - txsLock.Lock() - txs = append(txs, etxs...) - txsLock.Unlock() - wg.Done() - }(as) + etxs := as.findTxs(states, filter) + txs = append(txs, etxs...) } - wg.Wait() var minBroadcastBeforeBlockNum int64 for _, tx := range txs { - if *tx.TxAttempts[0].BroadcastBeforeBlockNum < minBroadcastBeforeBlockNum { + if minBroadcastBeforeBlockNum == 0 || *tx.TxAttempts[0].BroadcastBeforeBlockNum < minBroadcastBeforeBlockNum { minBroadcastBeforeBlockNum = *tx.TxAttempts[0].BroadcastBeforeBlockNum } } + if minBroadcastBeforeBlockNum == 0 { + return null.Int{}, nil + } return null.IntFrom(minBroadcastBeforeBlockNum), nil } diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a034e279771..1ea14607ac6 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -975,7 +975,84 @@ func TestInMemoryStore_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { require.True(t, actBroadcastAt.Valid) assert.Equal(t, expBroadcastAt.Time.Unix(), actBroadcastAt.Time.Unix()) }) + t.Run("wrong chain ID", func(t *testing.T) { + wrongChainID := big.NewInt(999) + ctx := testutils.Context(t) + expBroadcastAt, expErr := persistentStore.FindEarliestUnconfirmedBroadcastTime(ctx, wrongChainID) + actBroadcastAt, actErr := inMemoryStore.FindEarliestUnconfirmedBroadcastTime(ctx, wrongChainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.False(t, expBroadcastAt.Valid) + assert.False(t, actBroadcastAt.Valid) + }) + +} + +func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("no results", func(t *testing.T) { + ctx := testutils.Context(t) + expBlock, expErr := persistentStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, chainID) + actBlock, actErr := inMemoryStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.False(t, expBlock.Valid) + assert.False(t, actBlock.Valid) + }) + t.Run("find earliest unconfirmed tx block", func(t *testing.T) { + broadcastBeforeBlockNum := int64(2) + // insert the transaction into the persistent store + inTx := cltest.MustInsertUnconfirmedEthTx(t, persistentStore, 123, fromAddress) + attempt := cltest.NewLegacyEthTxAttempt(t, inTx.ID) + attempt.BroadcastBeforeBlockNum = &broadcastBeforeBlockNum + attempt.State = txmgrtypes.TxAttemptBroadcast + require.NoError(t, persistentStore.InsertTxAttempt(&attempt)) + inTx.TxAttempts = append(inTx.TxAttempts, attempt) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + expBlock, expErr := persistentStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, chainID) + actBlock, actErr := inMemoryStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.True(t, expBlock.Valid) + assert.True(t, actBlock.Valid) + assert.Equal(t, expBlock.Int64, actBlock.Int64) + }) + + t.Run("wrong chain ID", func(t *testing.T) { + wrongChainID := big.NewInt(999) + ctx := testutils.Context(t) + expBlock, expErr := persistentStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, wrongChainID) + actBlock, actErr := inMemoryStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, wrongChainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.False(t, expBlock.Valid) + assert.False(t, actBlock.Valid) + }) } // assertTxEqual asserts that two transactions are equal From 2011e066ad1b84e63020e6a4a92217e4874a655f Mon Sep 17 00:00:00 2001 From: James Walker Date: Wed, 20 Mar 2024 00:08:01 -0400 Subject: [PATCH 52/84] implement tests for LoadTxAttempts --- common/txmgr/inmemory_store.go | 2 +- .../evm/txmgr/evm_inmemory_store_test.go | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 87cb1ad8a9a..31dc5658d09 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -948,7 +948,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) LoadT defer ms.addressStatesLock.RUnlock() as, ok := ms.addressStates[etx.FromAddress] if !ok { - return fmt.Errorf("load_tx_attempts: %w", ErrAddressNotFound) + return nil } filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 1ea14607ac6..e7bb3b81bb9 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1055,6 +1055,52 @@ func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { }) } +func TestInMemoryStore_LoadTxAttempts(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("load tx attempt", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := mustInsertConfirmedMissingReceiptEthTxWithLegacyAttempt(t, persistentStore, 1, 7, time.Now(), fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + expTx := evmtxmgr.Tx{ID: inTx.ID, TxAttempts: []evmtxmgr.TxAttempt{}, FromAddress: fromAddress} // empty tx attempts for test + expErr := persistentStore.LoadTxAttempts(ctx, &expTx) + require.Equal(t, 1, len(expTx.TxAttempts)) + expAttempt := expTx.TxAttempts[0] + + actTx := evmtxmgr.Tx{ID: inTx.ID, TxAttempts: []evmtxmgr.TxAttempt{}, FromAddress: fromAddress} // empty tx attempts for test + actErr := inMemoryStore.LoadTxAttempts(ctx, &actTx) + require.Equal(t, 1, len(actTx.TxAttempts)) + actAttempt := actTx.TxAttempts[0] + + require.NoError(t, expErr) + require.NoError(t, actErr) + assertTxAttemptEqual(t, expAttempt, actAttempt) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From c28a5e7f84c3f59c3324e0e88ed0fe26bd0fc81a Mon Sep 17 00:00:00 2001 From: James Walker Date: Wed, 20 Mar 2024 00:18:08 -0400 Subject: [PATCH 53/84] implement tests for PreloadTxes --- common/txmgr/inmemory_store.go | 2 +- .../evm/txmgr/evm_inmemory_store_test.go | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 31dc5658d09..c4231161ed7 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -973,7 +973,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Prelo defer ms.addressStatesLock.RUnlock() as, ok := ms.addressStates[attempts[0].Tx.FromAddress] if !ok { - return fmt.Errorf("preload_txes: %w", ErrAddressNotFound) + return nil } txIDs := make([]int64, len(attempts)) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index e7bb3b81bb9..97796b09fa0 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1101,6 +1101,52 @@ func TestInMemoryStore_LoadTxAttempts(t *testing.T) { }) } +func TestInMemoryStore_PreloadTxes(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("load transaction", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, persistentStore, int64(7), fromAddress) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + expAttempts := []evmtxmgr.TxAttempt{{ID: 0, TxID: inTx.ID}} + expErr := persistentStore.PreloadTxes(ctx, expAttempts) + require.Equal(t, 1, len(expAttempts)) + expAttempt := expAttempts[0] + + actAttempts := []evmtxmgr.TxAttempt{{ID: 0, TxID: inTx.ID}} + actErr := inMemoryStore.PreloadTxes(ctx, actAttempts) + require.Equal(t, 1, len(actAttempts)) + actAttempt := actAttempts[0] + + require.NoError(t, expErr) + require.NoError(t, actErr) + assertTxAttemptEqual(t, expAttempt, actAttempt) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From bf5299fec2f74d0abb6b244dac7c7a21892ed391 Mon Sep 17 00:00:00 2001 From: James Walker Date: Wed, 20 Mar 2024 00:40:17 -0400 Subject: [PATCH 54/84] implement tests for IsTxFinalized --- common/txmgr/inmemory_store.go | 7 +- .../evm/txmgr/evm_inmemory_store_test.go | 76 +++++++++++++++++++ 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index c4231161ed7..175eef84895 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -1011,7 +1011,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Updat } func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) IsTxFinalized(ctx context.Context, blockHeight int64, txID int64, chainID CHAIN_ID) (bool, error) { if ms.chainID.String() != chainID.String() { - return false, fmt.Errorf("is_tx_finalized: %w", ErrInvalidChainID) + return false, nil } txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { @@ -1020,21 +1020,20 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) IsTxF } for _, attempt := range tx.TxAttempts { - if attempt.Receipts == nil || len(attempt.Receipts) == 0 { + if len(attempt.Receipts) == 0 { continue } // there can only be one receipt per attempt if attempt.Receipts[0].GetBlockNumber() == nil { continue } - return attempt.Receipts[0].GetBlockNumber().Int64() <= (blockHeight - int64(tx.MinConfirmations.Uint32)) } return false } txAttemptFilter := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - return attempt.Receipts != nil && len(attempt.Receipts) > 0 + return len(attempt.Receipts) > 0 } ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 97796b09fa0..e36cd78f9d5 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1147,6 +1147,82 @@ func TestInMemoryStore_PreloadTxes(t *testing.T) { }) } +func TestInMemoryStore_IsTxFinalized(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("tx not past finality depth", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 111, 1, fromAddress) + rec := mustInsertEthReceipt(t, persistentStore, 1, utils.NewHash(), inTx.TxAttempts[0].Hash) + // insert the transaction into the in-memory store + inTx.TxAttempts[0].Receipts = append(inTx.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + blockHeight := int64(2) + expIsFinalized, expErr := persistentStore.IsTxFinalized(ctx, blockHeight, inTx.ID, chainID) + actIsFinalized, actErr := inMemoryStore.IsTxFinalized(ctx, blockHeight, inTx.ID, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, expIsFinalized, actIsFinalized) + }) + + t.Run("tx is past finality depth", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 122, 2, fromAddress) + rec := mustInsertEthReceipt(t, persistentStore, 2, utils.NewHash(), inTx.TxAttempts[0].Hash) + // insert the transaction into the in-memory store + inTx.TxAttempts[0].Receipts = append(inTx.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + blockHeight := int64(10) + expIsFinalized, expErr := persistentStore.IsTxFinalized(ctx, blockHeight, inTx.ID, chainID) + actIsFinalized, actErr := inMemoryStore.IsTxFinalized(ctx, blockHeight, inTx.ID, chainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, expIsFinalized, actIsFinalized) + }) + + t.Run("wrong chain ID", func(t *testing.T) { + // insert the transaction into the persistent store + inTx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 133, 3, fromAddress) + rec := mustInsertEthReceipt(t, persistentStore, 3, utils.NewHash(), inTx.TxAttempts[0].Hash) + // insert the transaction into the in-memory store + inTx.TxAttempts[0].Receipts = append(inTx.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + ctx := testutils.Context(t) + blockHeight := int64(10) + wrongChainID := big.NewInt(999) + expIsFinalized, expErr := persistentStore.IsTxFinalized(ctx, blockHeight, inTx.ID, wrongChainID) + actIsFinalized, actErr := inMemoryStore.IsTxFinalized(ctx, blockHeight, inTx.ID, wrongChainID) + require.NoError(t, expErr) + require.NoError(t, actErr) + assert.Equal(t, expIsFinalized, actIsFinalized) + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 47c1e9adcbc347cfd4401cc285f95ce8aa0956dc Mon Sep 17 00:00:00 2001 From: James Walker Date: Wed, 20 Mar 2024 22:03:43 -0400 Subject: [PATCH 55/84] implement tests for FindTxsRequiringGasBump --- common/txmgr/inmemory_store.go | 31 +++++----- .../evm/txmgr/evm_inmemory_store_test.go | 57 +++++++++++++++++++ 2 files changed, 73 insertions(+), 15 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 175eef84895..aa753d47430 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -1049,7 +1049,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) IsTxF func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxsRequiringGasBump(ctx context.Context, address ADDR, blockNum, gasBumpThreshold, depth int64, chainID CHAIN_ID) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("find_txs_requiring_gas_bump: %w", ErrInvalidChainID) + return nil, nil } if gasBumpThreshold == 0 { return nil, nil @@ -1059,31 +1059,32 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT defer ms.addressStatesLock.RUnlock() as, ok := ms.addressStates[address] if !ok { - return nil, fmt.Errorf("find_txs_requiring_gas_bump: %w", ErrAddressNotFound) + return nil, nil } filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { - return false - } - attempt := tx.TxAttempts[0] - if *attempt.BroadcastBeforeBlockNum <= blockNum || - attempt.State == txmgrtypes.TxAttemptBroadcast { - return false + if len(tx.TxAttempts) == 0 { + return true } - - if tx.State != TxUnconfirmed || - attempt.ID != 0 { - return false + for _, attempt := range tx.TxAttempts { + if attempt.BroadcastBeforeBlockNum == nil || *attempt.BroadcastBeforeBlockNum > blockNum-gasBumpThreshold || attempt.State != txmgrtypes.TxAttemptBroadcast { + return false + } } return true } states := []txmgrtypes.TxState{TxUnconfirmed} txs := as.findTxs(states, filter) + // sort by sequence ASC - sort.Slice(txs, func(i, j int) bool { - return (*txs[i].Sequence).Int64() < (*txs[j].Sequence).Int64() + slices.SortFunc(txs, func(a, b txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { + aSequence, bSequence := a.Sequence, b.Sequence + if aSequence == nil || bSequence == nil { + return 0 + } + + return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) }) if depth > 0 { diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index e36cd78f9d5..d04f6ee943c 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -2,6 +2,7 @@ package txmgr_test import ( "context" + "fmt" "math/big" "testing" "time" @@ -1223,6 +1224,62 @@ func TestInMemoryStore_IsTxFinalized(t *testing.T) { }) } +func TestInMemoryStore_FindTxsRequiringGasBump(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := context.Background() + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + t.Run("gets transactions requiring gas bumping", func(t *testing.T) { + currentBlockNum := int64(10) + + // insert the transaction into the persistent store + inTx_0 := mustInsertUnconfirmedEthTxWithAttemptState(t, persistentStore, 1, fromAddress, txmgrtypes.TxAttemptBroadcast) + require.NoError(t, persistentStore.SetBroadcastBeforeBlockNum(testutils.Context(t), currentBlockNum, chainID)) + inTx_1 := mustInsertUnconfirmedEthTxWithAttemptState(t, persistentStore, 2, fromAddress, txmgrtypes.TxAttemptBroadcast) + require.NoError(t, persistentStore.SetBroadcastBeforeBlockNum(testutils.Context(t), currentBlockNum+1, chainID)) + // insert the transaction into the in-memory store + inTx_0.TxAttempts[0].BroadcastBeforeBlockNum = ¤tBlockNum + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) + tempCurrentBlockNum := currentBlockNum + 1 + inTx_1.TxAttempts[0].BroadcastBeforeBlockNum = &tempCurrentBlockNum + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_1)) + + ctx := testutils.Context(t) + newBlock := int64(12) + gasBumpThreshold := int64(2) + expTxs, expErr := persistentStore.FindTxsRequiringGasBump(ctx, fromAddress, newBlock, gasBumpThreshold, 0, chainID) + actTxs, actErr := inMemoryStore.FindTxsRequiringGasBump(ctx, fromAddress, newBlock, gasBumpThreshold, 0, chainID) + require.NoError(t, expErr) + fmt.Println("EXPTX", expTxs[0].ID) + for i := 0; i < len(expTxs[0].TxAttempts); i++ { + fmt.Println("EXPTX_ATTEMPT", expTxs[0].TxAttempts[i].ID) + } + require.NoError(t, actErr) + require.Equal(t, len(expTxs), len(actTxs)) + for i := 0; i < len(expTxs); i++ { + assertTxEqual(t, *expTxs[i], *actTxs[i]) + } + }) +} + // assertTxEqual asserts that two transactions are equal func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.ID, act.ID) From 94b9c6bf1f6df79adc3ffbb3038c6c3814bc8dfa Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 21 Mar 2024 00:03:55 -0400 Subject: [PATCH 56/84] address comments --- common/txmgr/inmemory_store.go | 5 +++-- core/chains/evm/txmgr/evm_inmemory_store_test.go | 9 ++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 88905f463b0..3272780517d 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -205,7 +205,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Close // Abandon removes all transactions for a given address func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Abandon(ctx context.Context, chainID CHAIN_ID, addr ADDR) error { if ms.chainID.String() != chainID.String() { - return fmt.Errorf("abandon: %w", ErrInvalidChainID) + panic("invalid chain ID") } // Mark all persisted transactions as abandoned @@ -218,7 +218,8 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Aband defer ms.addressStatesLock.RUnlock() as, ok := ms.addressStates[addr] if !ok { - return fmt.Errorf("abandon: %w", ErrAddressNotFound) + as = newAddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE](ms.lggr, chainID, addr, ms.maxUnstarted, nil) + ms.addressStates[addr] = as } as.abandon() diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index a8d1c2f4745..c36da4d3929 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -26,7 +26,7 @@ func TestInMemoryStore_Abandon(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -44,13 +44,12 @@ func TestInMemoryStore_Abandon(t *testing.T) { ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) - t.Run("saves new in_progress attempt if attempt is new", func(t *testing.T) { + t.Run("Abandon transactions successfully", func(t *testing.T) { nTxs := 3 - // Insert a transaction into persistent store for i := 0; i < nTxs; i++ { inTx := cltest.NewEthTx(fromAddress) // insert the transaction into the persistent store - require.NoError(t, persistentStore.InsertTx(&inTx)) + require.NoError(t, persistentStore.InsertTx(ctx, &inTx)) // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) } @@ -65,7 +64,7 @@ func TestInMemoryStore_Abandon(t *testing.T) { require.NotNil(t, expTxs) require.Equal(t, nTxs, len(expTxs)) - // Check that the in-memory store has the new attempt + // Check the in-memory store fn := func(tx *evmtxmgr.Tx) bool { return true } actTxs := inMemoryStore.XXXTestFindTxs(nil, fn) require.NotNil(t, actTxs) From 23dbf19d3d4227476fb38bd0f523b637b370c8f9 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 21 Mar 2024 00:06:31 -0400 Subject: [PATCH 57/84] cleanup --- core/chains/evm/txmgr/evm_inmemory_store_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index c36da4d3929..7bed8d66efe 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,7 +1,6 @@ package txmgr_test import ( - "context" "math/big" "testing" @@ -33,7 +32,7 @@ func TestInMemoryStore_Abandon(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -54,8 +53,8 @@ func TestInMemoryStore_Abandon(t *testing.T) { require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) } - actErr := inMemoryStore.Abandon(testutils.Context(t), chainID, fromAddress) - expErr := persistentStore.Abandon(testutils.Context(t), chainID, fromAddress) + actErr := inMemoryStore.Abandon(ctx, chainID, fromAddress) + expErr := persistentStore.Abandon(ctx, chainID, fromAddress) require.NoError(t, actErr) require.NoError(t, expErr) From 354e10fc4afbfaadf025a4960569a447d92f67b1 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 21 Mar 2024 00:11:59 -0400 Subject: [PATCH 58/84] address comments --- common/txmgr/inmemory_store.go | 2 +- .../evm/txmgr/evm_inmemory_store_test.go | 22 ++++++------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 235befc1095..16d19c540e6 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -200,7 +200,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Aband // SetBroadcastBeforeBlockNum sets the broadcast_before_block_num for a given chain ID func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SetBroadcastBeforeBlockNum(ctx context.Context, blockNum int64, chainID CHAIN_ID) error { if ms.chainID.String() != chainID.String() { - return nil + panic("invalid chain ID") } // Persist to persistent storage diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 3f6e4f31e3b..afe53cbaaa0 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,7 +1,6 @@ package txmgr_test import ( - "context" "math/big" "testing" @@ -26,14 +25,14 @@ func TestInMemoryStore_SetBroadcastBeforeBlockNum(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -51,10 +50,10 @@ func TestInMemoryStore_SetBroadcastBeforeBlockNum(t *testing.T) { require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) headNum := int64(9000) - err := inMemoryStore.SetBroadcastBeforeBlockNum(testutils.Context(t), headNum, chainID) + err := inMemoryStore.SetBroadcastBeforeBlockNum(ctx, headNum, chainID) require.NoError(t, err) - expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) require.NoError(t, err) require.Equal(t, 1, len(expTx.TxAttempts)) assert.Equal(t, headNum, *expTx.TxAttempts[0].BroadcastBeforeBlockNum) @@ -63,13 +62,6 @@ func TestInMemoryStore_SetBroadcastBeforeBlockNum(t *testing.T) { require.Equal(t, 1, len(actTxs)) actTx := actTxs[0] assertTxEqual(t, expTx, actTx) - - // wrong chain ID - wrongChainID := big.NewInt(123) - actErr := inMemoryStore.SetBroadcastBeforeBlockNum(testutils.Context(t), headNum, wrongChainID) - expErr := persistentStore.SetBroadcastBeforeBlockNum(testutils.Context(t), headNum, wrongChainID) - assert.NoError(t, actErr) - assert.NoError(t, expErr) }) t.Run("does not change evm.tx_attempts that already have BroadcastBeforeBlockNum set", func(t *testing.T) { @@ -78,16 +70,16 @@ func TestInMemoryStore_SetBroadcastBeforeBlockNum(t *testing.T) { inTx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, persistentStore, 11, fromAddress) inTxAttempt := newBroadcastLegacyEthTxAttempt(t, inTx.ID, 2) inTxAttempt.BroadcastBeforeBlockNum = &n - require.NoError(t, persistentStore.InsertTxAttempt(&inTxAttempt)) + require.NoError(t, persistentStore.InsertTxAttempt(ctx, &inTxAttempt)) // Insert the transaction into the in-memory store inTx.TxAttempts = append([]evmtxmgr.TxAttempt{inTxAttempt}, inTx.TxAttempts...) require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) headNum := int64(9000) - err := inMemoryStore.SetBroadcastBeforeBlockNum(testutils.Context(t), headNum, chainID) + err := inMemoryStore.SetBroadcastBeforeBlockNum(ctx, headNum, chainID) require.NoError(t, err) - expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) require.NoError(t, err) require.Equal(t, 2, len(expTx.TxAttempts)) assert.Equal(t, n, *expTx.TxAttempts[0].BroadcastBeforeBlockNum) From 83ca9e9a7979356ee4998e93061e6f35df27b105 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 21 Mar 2024 00:15:33 -0400 Subject: [PATCH 59/84] address comments --- core/chains/evm/txmgr/evm_inmemory_store_test.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 12a462e9806..6099493340d 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,7 +1,6 @@ package txmgr_test import ( - "context" "math/big" "testing" "time" @@ -27,14 +26,14 @@ func TestInMemoryStore_UpdateBroadcastAts(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -54,13 +53,13 @@ func TestInMemoryStore_UpdateBroadcastAts(t *testing.T) { require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) err := inMemoryStore.UpdateBroadcastAts( - testutils.Context(t), + ctx, now, []int64{inTx.ID}, ) require.NoError(t, err) - expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) require.NoError(t, err) fn := func(tx *evmtxmgr.Tx) bool { return true } actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) @@ -78,19 +77,19 @@ func TestInMemoryStore_UpdateBroadcastAts(t *testing.T) { inTx.State = commontxmgr.TxUnconfirmed inTx.BroadcastAt = &time1 inTx.InitialBroadcastAt = &time1 - require.NoError(t, persistentStore.InsertTx(&inTx)) + require.NoError(t, persistentStore.InsertTx(ctx, &inTx)) // Insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) time2 := time1.Add(1 * time.Hour) err := inMemoryStore.UpdateBroadcastAts( - testutils.Context(t), + ctx, time2, []int64{inTx.ID}, ) require.NoError(t, err) - expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) require.NoError(t, err) fn := func(tx *evmtxmgr.Tx) bool { return true } actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) From 311e1aa7dac57c4c239d0614fb07fee16cfe5489 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 21 Mar 2024 00:18:49 -0400 Subject: [PATCH 60/84] address comments --- common/txmgr/inmemory_store.go | 2 +- .../evm/txmgr/evm_inmemory_store_test.go | 20 ++++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 60c834aa741..5aabec7fcd3 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -237,7 +237,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateTxCallbackCompleted(ctx context.Context, pipelineTaskRunRid uuid.UUID, chainId CHAIN_ID) error { if ms.chainID.String() != chainId.String() { - return nil + panic("invalid chain ID") } // Persist to persistent storage diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index b7c1901156a..ec65791afbe 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,7 +1,6 @@ package txmgr_test import ( - "context" "math/big" "testing" @@ -27,14 +26,14 @@ func TestInMemoryStore_UpdateTxCallbackCompleted(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -49,7 +48,7 @@ func TestInMemoryStore_UpdateTxCallbackCompleted(t *testing.T) { // Insert a transaction into persistent store inTx := cltest.NewEthTx(fromAddress) inTx.PipelineTaskRunID = uuid.NullUUID{UUID: uuid.New(), Valid: true} - require.NoError(t, persistentStore.InsertTx(&inTx)) + require.NoError(t, persistentStore.InsertTx(ctx, &inTx)) // Insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) @@ -60,7 +59,7 @@ func TestInMemoryStore_UpdateTxCallbackCompleted(t *testing.T) { ) require.NoError(t, err) - expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) require.NoError(t, err) fn := func(tx *evmtxmgr.Tx) bool { return true } actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) @@ -69,17 +68,10 @@ func TestInMemoryStore_UpdateTxCallbackCompleted(t *testing.T) { assertTxEqual(t, expTx, actTx) assert.True(t, actTx.CallbackCompleted) - // wrong chain id - wrongChainID := big.NewInt(123) - actErr := inMemoryStore.UpdateTxCallbackCompleted(testutils.Context(t), inTx.PipelineTaskRunID.UUID, wrongChainID) - expErr := persistentStore.UpdateTxCallbackCompleted(testutils.Context(t), inTx.PipelineTaskRunID.UUID, wrongChainID) - assert.NoError(t, actErr) - assert.NoError(t, expErr) - // wrong PipelineTaskRunID wrongPipelineTaskRunID := uuid.NullUUID{UUID: uuid.New(), Valid: true} - actErr = inMemoryStore.UpdateTxCallbackCompleted(testutils.Context(t), wrongPipelineTaskRunID.UUID, chainID) - expErr = persistentStore.UpdateTxCallbackCompleted(testutils.Context(t), wrongPipelineTaskRunID.UUID, chainID) + actErr := inMemoryStore.UpdateTxCallbackCompleted(ctx, wrongPipelineTaskRunID.UUID, chainID) + expErr := persistentStore.UpdateTxCallbackCompleted(ctx, wrongPipelineTaskRunID.UUID, chainID) assert.NoError(t, actErr) assert.NoError(t, expErr) }) From 69d0a9c44eafc22d153acfe619546fe131a3651c Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 21 Mar 2024 00:35:12 -0400 Subject: [PATCH 61/84] address comments --- common/txmgr/inmemory_store.go | 18 +++++++--------- .../evm/txmgr/evm_inmemory_store_test.go | 21 +++++++++---------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 3afa55fd9da..a9e152efdb0 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -327,14 +327,20 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveI ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() + if attempt.State != txmgrtypes.TxAttemptInProgress { + return fmt.Errorf("SaveInProgressAttempt failed: attempt state must be in_progress") + } + var tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] - for _, as := range ms.addressStates { + var as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] + for _, vas := range ms.addressStates { fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { return true } - txs := as.findTxs(nil, fn, attempt.TxID) + txs := vas.findTxs(nil, fn, attempt.TxID) if len(txs) != 0 { tx = &txs[0] + as = vas break } } @@ -342,14 +348,6 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveI return fmt.Errorf("save_in_progress_attempt: %w: with attempt hash %q", ErrTxnNotFound, attempt.Hash) } - as, ok := ms.addressStates[tx.FromAddress] - if !ok { - return fmt.Errorf("save_in_progress_attempt: %w", ErrAddressNotFound) - } - if attempt.State != txmgrtypes.TxAttemptInProgress { - return fmt.Errorf("SaveInProgressAttempt failed: attempt state must be in_progress") - } - // Persist to persistent storage if err := ms.persistentTxStore.SaveInProgressAttempt(ctx, attempt); err != nil { return err diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index ea725cb9f9a..5cbe967d95f 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,7 +1,6 @@ package txmgr_test import ( - "context" "math/big" "testing" @@ -27,14 +26,14 @@ func TestInMemoryStore_SaveInProgressAttempt(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -55,10 +54,10 @@ func TestInMemoryStore_SaveInProgressAttempt(t *testing.T) { inTxAttempt := cltest.NewLegacyEthTxAttempt(t, inTx.ID) require.Equal(t, int64(0), inTxAttempt.ID) - err := inMemoryStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) + err := inMemoryStore.SaveInProgressAttempt(ctx, &inTxAttempt) require.NoError(t, err) - expTxAttempt, err := persistentStore.FindTxAttempt(inTxAttempt.Hash) + expTxAttempt, err := persistentStore.FindTxAttempt(ctx, inTxAttempt.Hash) require.NoError(t, err) assert.Equal(t, txmgrtypes.TxAttemptInProgress, expTxAttempt.State) @@ -87,10 +86,10 @@ func TestInMemoryStore_SaveInProgressAttempt(t *testing.T) { inTxAttempt.BroadcastBeforeBlockNum = nil inTxAttempt.State = txmgrtypes.TxAttemptInProgress - err := inMemoryStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) + err := inMemoryStore.SaveInProgressAttempt(ctx, &inTxAttempt) require.NoError(t, err) - expTxAttempt, err := persistentStore.FindTxAttempt(inTxAttempt.Hash) + expTxAttempt, err := persistentStore.FindTxAttempt(ctx, inTxAttempt.Hash) require.NoError(t, err) assert.Equal(t, txmgrtypes.TxAttemptInProgress, expTxAttempt.State) @@ -118,16 +117,16 @@ func TestInMemoryStore_SaveInProgressAttempt(t *testing.T) { // wrong tx id inTxAttempt.TxID = 999 - actErr := inMemoryStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) - expErr := persistentStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) + actErr := inMemoryStore.SaveInProgressAttempt(ctx, &inTxAttempt) + expErr := persistentStore.SaveInProgressAttempt(ctx, &inTxAttempt) assert.Error(t, actErr) assert.Error(t, expErr) inTxAttempt.TxID = inTx.ID // reset // wrong state inTxAttempt.State = txmgrtypes.TxAttemptBroadcast - actErr = inMemoryStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) - expErr = persistentStore.SaveInProgressAttempt(testutils.Context(t), &inTxAttempt) + actErr = inMemoryStore.SaveInProgressAttempt(ctx, &inTxAttempt) + expErr = persistentStore.SaveInProgressAttempt(ctx, &inTxAttempt) assert.Error(t, actErr) assert.Error(t, expErr) assert.Equal(t, expErr, actErr) From 7b4e81aed769ec20965370ca8878e56bdbf55384 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 21 Mar 2024 00:41:05 -0400 Subject: [PATCH 62/84] address comments --- common/txmgr/inmemory_store.go | 8 ++++---- .../chains/evm/txmgr/evm_inmemory_store_test.go | 17 ++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 756d019b0fa..6bed27db6d2 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -330,6 +330,10 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveI ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() + if !(attempt.State == txmgrtypes.TxAttemptInProgress || attempt.State == txmgrtypes.TxAttemptInsufficientFunds) { + return fmt.Errorf("expected state to be in_progress or insufficient_funds") + } + var tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] var as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { return true } @@ -345,10 +349,6 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveI return nil } - if !(attempt.State == txmgrtypes.TxAttemptInProgress || attempt.State == txmgrtypes.TxAttemptInsufficientFunds) { - return fmt.Errorf("expected state to be in_progress or insufficient_funds") - } - // Persist to persistent storage if err := ms.persistentTxStore.SaveInsufficientFundsAttempt(ctx, timeout, attempt, broadcastAt); err != nil { return err diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 57d9614520d..2a33ab1733e 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,7 +1,6 @@ package txmgr_test import ( - "context" "math/big" "testing" "time" @@ -28,14 +27,14 @@ func TestInMemoryStore_SaveInsufficientFundsAttempt(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -55,14 +54,14 @@ func TestInMemoryStore_SaveInsufficientFundsAttempt(t *testing.T) { require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) err := inMemoryStore.SaveInsufficientFundsAttempt( - testutils.Context(t), + ctx, defaultDuration, &inTx.TxAttempts[0], now, ) require.NoError(t, err) - expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) require.NoError(t, err) fn := func(tx *evmtxmgr.Tx) bool { return true } actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) @@ -73,16 +72,16 @@ func TestInMemoryStore_SaveInsufficientFundsAttempt(t *testing.T) { // wrong tx id inTx.TxAttempts[0].TxID = 123 - actErr := inMemoryStore.SaveInsufficientFundsAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) - expErr := persistentStore.SaveInsufficientFundsAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + actErr := inMemoryStore.SaveInsufficientFundsAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) + expErr := persistentStore.SaveInsufficientFundsAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) assert.NoError(t, actErr) assert.NoError(t, expErr) inTx.TxAttempts[0].TxID = inTx.ID // reset // wrong attempt state inTx.TxAttempts[0].State = txmgrtypes.TxAttemptBroadcast - actErr = inMemoryStore.SaveInsufficientFundsAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) - expErr = persistentStore.SaveInsufficientFundsAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + actErr = inMemoryStore.SaveInsufficientFundsAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) + expErr = persistentStore.SaveInsufficientFundsAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) assert.Error(t, actErr) assert.Error(t, expErr) inTx.TxAttempts[0].State = txmgrtypes.TxAttemptInsufficientFunds // reset From e30f622f1fc27f1393a7dbcff18713ac974cfa00 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 21 Mar 2024 00:47:22 -0400 Subject: [PATCH 63/84] address comments --- common/txmgr/inmemory_store.go | 13 +++++-------- .../chains/evm/txmgr/evm_inmemory_store_test.go | 17 ++++++++--------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index b0c58e048b0..fff742901e9 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -333,6 +333,10 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveS ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() + if attempt.State != txmgrtypes.TxAttemptInProgress { + return fmt.Errorf("expected state to be in_progress") + } + var tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] var as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { return true } @@ -348,10 +352,6 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveS return fmt.Errorf("save_sent_attempt: %w", ErrTxnNotFound) } - if attempt.State != txmgrtypes.TxAttemptInProgress { - return fmt.Errorf("expected state to be in_progress") - } - // Persist to persistent storage if err := ms.persistentTxStore.SaveSentAttempt(ctx, timeout, attempt, broadcastAt); err != nil { return err @@ -359,10 +359,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveS // Update in memory store fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { - if tx.ID != attempt.TxID { - return - } - if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + if len(tx.TxAttempts) == 0 { return } if tx.BroadcastAt != nil && tx.BroadcastAt.Before(broadcastAt) { diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index f080c4bd12a..4dd163dec44 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,7 +1,6 @@ package txmgr_test import ( - "context" "math/big" "testing" "time" @@ -28,14 +27,14 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -56,14 +55,14 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) err := inMemoryStore.SaveSentAttempt( - testutils.Context(t), + ctx, defaultDuration, &inTx.TxAttempts[0], now, ) require.NoError(t, err) - expTx, err := persistentStore.FindTxWithAttempts(inTx.ID) + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) require.NoError(t, err) fn := func(tx *evmtxmgr.Tx) bool { return true } actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) @@ -74,16 +73,16 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { // wrong tx id inTx.TxAttempts[0].TxID = 123 - actErr := inMemoryStore.SaveSentAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) - expErr := persistentStore.SaveSentAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + actErr := inMemoryStore.SaveSentAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) + expErr := persistentStore.SaveSentAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) assert.Error(t, actErr) assert.Error(t, expErr) inTx.TxAttempts[0].TxID = inTx.ID // reset // wrong attempt state inTx.TxAttempts[0].State = txmgrtypes.TxAttemptBroadcast - actErr = inMemoryStore.SaveSentAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) - expErr = persistentStore.SaveSentAttempt(testutils.Context(t), defaultDuration, &inTx.TxAttempts[0], now) + actErr = inMemoryStore.SaveSentAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) + expErr = persistentStore.SaveSentAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) assert.Error(t, actErr) assert.Error(t, expErr) inTx.TxAttempts[0].State = txmgrtypes.TxAttemptInProgress // reset From 5ea7b15b01b34dabdf1812090cab9a550f644fac Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 21 Mar 2024 20:51:51 -0400 Subject: [PATCH 64/84] address comments --- common/txmgr/address_state.go | 55 +++++++++++++++ common/txmgr/inmemory_store.go | 37 +++++----- .../evm/txmgr/evm_inmemory_store_test.go | 70 +++++++++---------- 3 files changed, 107 insertions(+), 55 deletions(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index ed327b69960..e64857aecb3 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -1,6 +1,7 @@ package txmgr import ( + "fmt" "sync" "time" @@ -162,6 +163,60 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) findTx return as.idempotencyKeyToTx[key] } +// addInProgressTxAttempt saves the in-progress transaction attempt. +// The transaction attempt should have a valid ID assigned by the caller. +func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) addInProgressTxAttempt( + txAttempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], +) error { + as.Lock() + defer as.Unlock() + + if txAttempt.ID == 0 { + return fmt.Errorf("save_in_progress_tx_attempt: TxAttempt ID must be set") + } + + tx, ok := as.allTxs[txAttempt.TxID] + if !ok { + return ErrTxnNotFound + } + + // add the new attempt to the transaction + tx.TxAttempts = append(tx.TxAttempts, txAttempt) + as.attemptHashToTxAttempt[txAttempt.Hash] = &txAttempt + + return nil +} + +// updateInProgressTxAttempt saves the in-progress transaction attempt. +// The transaction attempt should have a valid ID assigned by the caller. +func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) updateInProgressTxAttempt( + txAttempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], +) error { + as.Lock() + defer as.Unlock() + + if txAttempt.ID == 0 { + return fmt.Errorf("save_in_progress_tx_attempt: TxAttempt ID must be set") + } + + tx, ok := as.allTxs[txAttempt.TxID] + if !ok { + return ErrTxnNotFound + } + + // update the existing attempt if it exists + for i := 0; i < len(tx.TxAttempts); i++ { + if tx.TxAttempts[i].ID == txAttempt.ID { + tx.TxAttempts[i].State = txmgrtypes.TxAttemptInProgress + tx.TxAttempts[i].BroadcastBeforeBlockNum = txAttempt.BroadcastBeforeBlockNum + as.attemptHashToTxAttempt[txAttempt.Hash] = &tx.TxAttempts[i] + return nil + } + } + + return fmt.Errorf("update_in_progress_tx_attempt: tried to update but no tx attempt found with ID %v", txAttempt.ID) +} + // applyToTxsByState calls the given function for each transaction in the given states. // If txIDs are provided, only the transactions with those IDs are considered. // If no txIDs are provided, all transactions in the given states are considered. diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index a9e152efdb0..f7b5767e6e8 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -81,11 +81,20 @@ func NewInMemoryStore[ ms.maxUnstarted = 10000 } + addressesToTxs := map[ADDR][]txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + // populate all enabled addresses + enabledAddresses, err := keyStore.EnabledAddressesForChain(ctx, chainID) + if err != nil { + return nil, fmt.Errorf("new_in_memory_store: %w", err) + } + for _, addr := range enabledAddresses { + addressesToTxs[addr] = []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + } + txs, err := persistentTxStore.GetAllTransactions(ctx, chainID) if err != nil { return nil, fmt.Errorf("address_state: initialization: %w", err) } - addressesToTxs := map[ADDR][]txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} for _, tx := range txs { at, exists := addressesToTxs[tx.FromAddress] if !exists { @@ -348,30 +357,24 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveI return fmt.Errorf("save_in_progress_attempt: %w: with attempt hash %q", ErrTxnNotFound, attempt.Hash) } + // Check if the attempt already exists by checking if id is zero + // this will be used by memory store + var txAttemptExists bool + if attempt.ID != 0 { + txAttemptExists = true + } + // Persist to persistent storage if err := ms.persistentTxStore.SaveInProgressAttempt(ctx, attempt); err != nil { return err } // Update in memory store - fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { - if tx.ID != attempt.TxID { - return - } - if tx.TxAttempts != nil && len(tx.TxAttempts) > 0 { - for i := 0; i < len(tx.TxAttempts); i++ { - if tx.TxAttempts[i].ID == attempt.ID { - tx.TxAttempts[i].State = txmgrtypes.TxAttemptInProgress - tx.TxAttempts[i].BroadcastBeforeBlockNum = attempt.BroadcastBeforeBlockNum - return - } - } - } - tx.TxAttempts = []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{*attempt} + if txAttemptExists { + return as.updateInProgressTxAttempt(*attempt) } - as.applyToTxsByState(nil, fn, attempt.TxID) - return nil + return as.addInProgressTxAttempt(*attempt) } func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveInsufficientFundsAttempt(ctx context.Context, timeout time.Duration, attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], broadcastAt time.Time) error { return nil diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 5cbe967d95f..67d9b5b5645 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -57,21 +57,17 @@ func TestInMemoryStore_SaveInProgressAttempt(t *testing.T) { err := inMemoryStore.SaveInProgressAttempt(ctx, &inTxAttempt) require.NoError(t, err) - expTxAttempt, err := persistentStore.FindTxAttempt(ctx, inTxAttempt.Hash) + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) require.NoError(t, err) - assert.Equal(t, txmgrtypes.TxAttemptInProgress, expTxAttempt.State) // Check that the in-memory store has the new attempt fn := func(tx *evmtxmgr.Tx) bool { return true } - txs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) - require.NotNil(t, txs) - require.Equal(t, 1, len(txs)) - - for _, actTxAttempt := range txs[0].TxAttempts { - if actTxAttempt.Hash == expTxAttempt.Hash { - assertTxAttemptEqual(t, *expTxAttempt, actTxAttempt) - } - } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.NotNil(t, actTxs) + actTx := actTxs[0] + require.Equal(t, len(expTx.TxAttempts), len(actTx.TxAttempts)) + + assertTxEqual(t, expTx, actTx) }) t.Run("updates old attempt to in_progress when insufficient_funds", func(t *testing.T) { // Insert a transaction into persistent store @@ -89,21 +85,17 @@ func TestInMemoryStore_SaveInProgressAttempt(t *testing.T) { err := inMemoryStore.SaveInProgressAttempt(ctx, &inTxAttempt) require.NoError(t, err) - expTxAttempt, err := persistentStore.FindTxAttempt(ctx, inTxAttempt.Hash) + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) require.NoError(t, err) - assert.Equal(t, txmgrtypes.TxAttemptInProgress, expTxAttempt.State) // Check that the in-memory store has the new attempt fn := func(tx *evmtxmgr.Tx) bool { return true } - txs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) - require.NotNil(t, txs) - require.Equal(t, 1, len(txs)) - - for _, actTxAttempt := range txs[0].TxAttempts { - if actTxAttempt.Hash == expTxAttempt.Hash { - assertTxAttemptEqual(t, *expTxAttempt, actTxAttempt) - } - } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.NotNil(t, actTxs) + actTx := actTxs[0] + require.Equal(t, len(expTx.TxAttempts), len(actTx.TxAttempts)) + + assertTxEqual(t, expTx, actTx) }) t.Run("handles errors the same way as the persistent store", func(t *testing.T) { // Insert a transaction into persistent store @@ -115,22 +107,24 @@ func TestInMemoryStore_SaveInProgressAttempt(t *testing.T) { inTxAttempt := cltest.NewLegacyEthTxAttempt(t, inTx.ID) require.Equal(t, int64(0), inTxAttempt.ID) - // wrong tx id - inTxAttempt.TxID = 999 - actErr := inMemoryStore.SaveInProgressAttempt(ctx, &inTxAttempt) - expErr := persistentStore.SaveInProgressAttempt(ctx, &inTxAttempt) - assert.Error(t, actErr) - assert.Error(t, expErr) - inTxAttempt.TxID = inTx.ID // reset - - // wrong state - inTxAttempt.State = txmgrtypes.TxAttemptBroadcast - actErr = inMemoryStore.SaveInProgressAttempt(ctx, &inTxAttempt) - expErr = persistentStore.SaveInProgressAttempt(ctx, &inTxAttempt) - assert.Error(t, actErr) - assert.Error(t, expErr) - assert.Equal(t, expErr, actErr) - inTxAttempt.State = txmgrtypes.TxAttemptInProgress // reset + t.Run("wrong tx id", func(t *testing.T) { + inTxAttempt.TxID = 999 + actErr := inMemoryStore.SaveInProgressAttempt(ctx, &inTxAttempt) + expErr := persistentStore.SaveInProgressAttempt(ctx, &inTxAttempt) + assert.Error(t, actErr) + assert.Error(t, expErr) + inTxAttempt.TxID = inTx.ID // reset + }) + + t.Run("wrong state", func(t *testing.T) { + inTxAttempt.State = txmgrtypes.TxAttemptBroadcast + actErr := inMemoryStore.SaveInProgressAttempt(ctx, &inTxAttempt) + expErr := persistentStore.SaveInProgressAttempt(ctx, &inTxAttempt) + assert.Error(t, actErr) + assert.Error(t, expErr) + assert.Equal(t, expErr, actErr) + inTxAttempt.State = txmgrtypes.TxAttemptInProgress // reset + }) }) } From 66e60da09f9f90c6853956ba40b4f831cb66f1aa Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 21 Mar 2024 23:44:34 -0400 Subject: [PATCH 65/84] clean up contexts to use testutils --- .../evm/txmgr/evm_inmemory_store_test.go | 85 +++++-------------- 1 file changed, 22 insertions(+), 63 deletions(-) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index d04f6ee943c..eef3308127e 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,7 +1,6 @@ package txmgr_test import ( - "context" "fmt" "math/big" "testing" @@ -38,7 +37,7 @@ func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -73,7 +72,6 @@ func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx := testutils.Context(t) actTx, actErr := inMemoryStore.FindTxWithIdempotencyKey(ctx, tc.inIdempotencyKey, tc.inChainID) expTx, expErr := persistentStore.FindTxWithIdempotencyKey(ctx, tc.inIdempotencyKey, tc.inChainID) require.Equal(t, expErr, actErr) @@ -105,7 +103,7 @@ func TestInMemoryStore_CheckTxQueueCapacity(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -145,7 +143,6 @@ func TestInMemoryStore_CheckTxQueueCapacity(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx := testutils.Context(t) actErr := inMemoryStore.CheckTxQueueCapacity(ctx, tc.inFromAddress, tc.inMaxQueuedTxs, tc.inChainID) expErr := persistentStore.CheckTxQueueCapacity(ctx, tc.inFromAddress, tc.inMaxQueuedTxs, tc.inChainID) if tc.hasErr { @@ -171,7 +168,7 @@ func TestInMemoryStore_CountUnstartedTransactions(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -209,7 +206,6 @@ func TestInMemoryStore_CountUnstartedTransactions(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx := testutils.Context(t) actMemoryCount, actErr := inMemoryStore.CountUnstartedTransactions(ctx, tc.inFromAddress, tc.inChainID) actPersistentCount, expErr := persistentStore.CountUnstartedTransactions(ctx, tc.inFromAddress, tc.inChainID) if tc.hasErr { @@ -237,7 +233,7 @@ func TestInMemoryStore_CountUnconfirmedTransactions(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -272,7 +268,6 @@ func TestInMemoryStore_CountUnconfirmedTransactions(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx := testutils.Context(t) actMemoryCount, actErr := inMemoryStore.CountUnconfirmedTransactions(ctx, tc.inFromAddress, tc.inChainID) actPersistentCount, expErr := persistentStore.CountUnconfirmedTransactions(ctx, tc.inFromAddress, tc.inChainID) if tc.hasErr { @@ -300,7 +295,7 @@ func TestInMemoryStore_FindTxAttemptsConfirmedMissingReceipt(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -344,7 +339,6 @@ func TestInMemoryStore_FindTxAttemptsConfirmedMissingReceipt(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx := testutils.Context(t) actTxAttempts, actErr := inMemoryStore.FindTxAttemptsConfirmedMissingReceipt(ctx, tc.inChainID) expTxAttempts, expErr := persistentStore.FindTxAttemptsConfirmedMissingReceipt(ctx, tc.inChainID) if tc.hasError { @@ -375,7 +369,7 @@ func TestInMemoryStore_FindTxAttemptsRequiringReceiptFetch(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -419,7 +413,6 @@ func TestInMemoryStore_FindTxAttemptsRequiringReceiptFetch(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - ctx := testutils.Context(t) expTxAttempts, expErr := persistentStore.FindTxAttemptsRequiringReceiptFetch(ctx, tc.inChainID) actTxAttempts, actErr := inMemoryStore.FindTxAttemptsRequiringReceiptFetch(ctx, tc.inChainID) if tc.hasError { @@ -450,7 +443,7 @@ func TestInMemoryStore_GetInProgressTxAttempts(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -462,7 +455,6 @@ func TestInMemoryStore_GetInProgressTxAttempts(t *testing.T) { require.NoError(t, err) t.Run("gets 0 in progress transaction", func(t *testing.T) { - ctx := testutils.Context(t) expTxAttempts, expErr := persistentStore.GetInProgressTxAttempts(ctx, fromAddress, chainID) actTxAttempts, actErr := inMemoryStore.GetInProgressTxAttempts(ctx, fromAddress, chainID) require.NoError(t, actErr) @@ -476,7 +468,6 @@ func TestInMemoryStore_GetInProgressTxAttempts(t *testing.T) { // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) expTxAttempts, expErr := persistentStore.GetInProgressTxAttempts(ctx, fromAddress, chainID) actTxAttempts, actErr := inMemoryStore.GetInProgressTxAttempts(ctx, fromAddress, chainID) require.NoError(t, expErr) @@ -500,7 +491,7 @@ func TestInMemoryStore_HasInProgressTransaction(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -512,7 +503,6 @@ func TestInMemoryStore_HasInProgressTransaction(t *testing.T) { require.NoError(t, err) t.Run("no in progress transaction", func(t *testing.T) { - ctx := testutils.Context(t) expExists, expErr := persistentStore.HasInProgressTransaction(ctx, fromAddress, chainID) actExists, actErr := inMemoryStore.HasInProgressTransaction(ctx, fromAddress, chainID) require.NoError(t, actErr) @@ -526,7 +516,6 @@ func TestInMemoryStore_HasInProgressTransaction(t *testing.T) { // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) expExists, expErr := persistentStore.HasInProgressTransaction(ctx, fromAddress, chainID) actExists, actErr := inMemoryStore.HasInProgressTransaction(ctx, fromAddress, chainID) require.NoError(t, expErr) @@ -547,7 +536,7 @@ func TestInMemoryStore_GetTxByID(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -559,7 +548,6 @@ func TestInMemoryStore_GetTxByID(t *testing.T) { require.NoError(t, err) t.Run("no transaction", func(t *testing.T) { - ctx := testutils.Context(t) expTx, expErr := persistentStore.GetTxByID(ctx, 0) actTx, actErr := inMemoryStore.GetTxByID(ctx, 0) require.NoError(t, expErr) @@ -574,7 +562,6 @@ func TestInMemoryStore_GetTxByID(t *testing.T) { // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) expTx, expErr := persistentStore.GetTxByID(ctx, inTx.ID) actTx, actErr := inMemoryStore.GetTxByID(ctx, inTx.ID) require.NoError(t, expErr) @@ -597,7 +584,7 @@ func TestInMemoryStore_FindTxWithSequence(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -609,7 +596,6 @@ func TestInMemoryStore_FindTxWithSequence(t *testing.T) { require.NoError(t, err) t.Run("no results", func(t *testing.T) { - ctx := testutils.Context(t) expTx, expErr := persistentStore.FindTxWithSequence(ctx, fromAddress, evmtypes.Nonce(666)) actTx, actErr := inMemoryStore.FindTxWithSequence(ctx, fromAddress, evmtypes.Nonce(666)) require.NoError(t, expErr) @@ -624,7 +610,6 @@ func TestInMemoryStore_FindTxWithSequence(t *testing.T) { // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) expTx, expErr := persistentStore.FindTxWithSequence(ctx, fromAddress, evmtypes.Nonce(666)) actTx, actErr := inMemoryStore.FindTxWithSequence(ctx, fromAddress, evmtypes.Nonce(666)) require.NoError(t, expErr) @@ -641,7 +626,6 @@ func TestInMemoryStore_FindTxWithSequence(t *testing.T) { require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) wrongFromAddress := common.Address{} - ctx := testutils.Context(t) expTx, expErr := persistentStore.FindTxWithSequence(ctx, wrongFromAddress, evmtypes.Nonce(777)) actTx, actErr := inMemoryStore.FindTxWithSequence(ctx, wrongFromAddress, evmtypes.Nonce(777)) require.NoError(t, expErr) @@ -663,7 +647,7 @@ func TestInMemoryStore_CountTransactionsByState(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -675,7 +659,6 @@ func TestInMemoryStore_CountTransactionsByState(t *testing.T) { require.NoError(t, err) t.Run("no results", func(t *testing.T) { - ctx := testutils.Context(t) expCount, expErr := persistentStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, chainID) actCount, actErr := inMemoryStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, chainID) require.NoError(t, expErr) @@ -684,7 +667,6 @@ func TestInMemoryStore_CountTransactionsByState(t *testing.T) { }) t.Run("wrong chain id", func(t *testing.T) { wrongChainID := big.NewInt(999) - ctx := testutils.Context(t) expCount, expErr := persistentStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, wrongChainID) actCount, actErr := inMemoryStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, wrongChainID) require.NoError(t, expErr) @@ -699,7 +681,6 @@ func TestInMemoryStore_CountTransactionsByState(t *testing.T) { require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) } - ctx := testutils.Context(t) expCount, expErr := persistentStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, chainID) actCount, actErr := inMemoryStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, chainID) require.NoError(t, expErr) @@ -721,7 +702,7 @@ func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testi ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -733,7 +714,6 @@ func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testi require.NoError(t, err) t.Run("no results", func(t *testing.T) { - ctx := testutils.Context(t) expTxs, expErr := persistentStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, chainID) actTxs, actErr := inMemoryStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, chainID) require.NoError(t, expErr) @@ -767,7 +747,6 @@ func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testi require.NoError(t, inMemoryStore.XXXTestInsertTx(otherAddress, &otx_3)) t.Run("return all eth_txes with at least one attempt that is in insufficient_eth state", func(t *testing.T) { - ctx := testutils.Context(t) expTxs, expErr := persistentStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, chainID) actTxs, actErr := inMemoryStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, chainID) require.NoError(t, expErr) @@ -781,7 +760,6 @@ func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testi t.Run("does not return txes with different chainID", func(t *testing.T) { wrongChainID := big.NewInt(999) - ctx := testutils.Context(t) expTxs, expErr := persistentStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, wrongChainID) actTxs, actErr := inMemoryStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, wrongChainID) require.NoError(t, expErr) @@ -791,7 +769,6 @@ func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testi t.Run("does not return txes with different fromAddress", func(t *testing.T) { anotherFromAddress := common.Address{} - ctx := testutils.Context(t) expTxs, expErr := persistentStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, anotherFromAddress, chainID) actTxs, actErr := inMemoryStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, anotherFromAddress, chainID) require.NoError(t, expErr) @@ -812,7 +789,7 @@ func TestInMemoryStore_GetNonFatalTransactions(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -824,7 +801,6 @@ func TestInMemoryStore_GetNonFatalTransactions(t *testing.T) { require.NoError(t, err) t.Run("no results", func(t *testing.T) { - ctx := testutils.Context(t) expTxs, expErr := persistentStore.GetNonFatalTransactions(ctx, chainID) actTxs, actErr := inMemoryStore.GetNonFatalTransactions(ctx, chainID) require.NoError(t, expErr) @@ -840,7 +816,6 @@ func TestInMemoryStore_GetNonFatalTransactions(t *testing.T) { require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_1)) - ctx := testutils.Context(t) expTxs, expErr := persistentStore.GetNonFatalTransactions(ctx, chainID) actTxs, actErr := inMemoryStore.GetNonFatalTransactions(ctx, chainID) require.NoError(t, expErr) @@ -854,7 +829,6 @@ func TestInMemoryStore_GetNonFatalTransactions(t *testing.T) { t.Run("wrong chain ID", func(t *testing.T) { wrongChainID := big.NewInt(999) - ctx := testutils.Context(t) expTxs, expErr := persistentStore.GetNonFatalTransactions(ctx, wrongChainID) actTxs, actErr := inMemoryStore.GetNonFatalTransactions(ctx, wrongChainID) require.NoError(t, expErr) @@ -875,7 +849,7 @@ func TestInMemoryStore_FindTransactionsConfirmedInBlockRange(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -887,7 +861,6 @@ func TestInMemoryStore_FindTransactionsConfirmedInBlockRange(t *testing.T) { require.NoError(t, err) t.Run("no results", func(t *testing.T) { - ctx := testutils.Context(t) expTxs, expErr := persistentStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, chainID) actTxs, actErr := inMemoryStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, chainID) require.NoError(t, expErr) @@ -907,7 +880,6 @@ func TestInMemoryStore_FindTransactionsConfirmedInBlockRange(t *testing.T) { inTx_1.TxAttempts[0].Receipts = append(inTx_1.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec_1)) require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_1)) - ctx := testutils.Context(t) expTxs, expErr := persistentStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, chainID) actTxs, actErr := inMemoryStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, chainID) require.NoError(t, expErr) @@ -920,7 +892,6 @@ func TestInMemoryStore_FindTransactionsConfirmedInBlockRange(t *testing.T) { t.Run("wrong chain ID", func(t *testing.T) { wrongChainID := big.NewInt(999) - ctx := testutils.Context(t) expTxs, expErr := persistentStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, wrongChainID) actTxs, actErr := inMemoryStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, wrongChainID) require.NoError(t, expErr) @@ -941,7 +912,7 @@ func TestInMemoryStore_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -953,7 +924,6 @@ func TestInMemoryStore_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { require.NoError(t, err) t.Run("no results", func(t *testing.T) { - ctx := testutils.Context(t) expBroadcastAt, expErr := persistentStore.FindEarliestUnconfirmedBroadcastTime(ctx, chainID) actBroadcastAt, actErr := inMemoryStore.FindEarliestUnconfirmedBroadcastTime(ctx, chainID) require.NoError(t, expErr) @@ -967,7 +937,6 @@ func TestInMemoryStore_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) expBroadcastAt, expErr := persistentStore.FindEarliestUnconfirmedBroadcastTime(ctx, chainID) actBroadcastAt, actErr := inMemoryStore.FindEarliestUnconfirmedBroadcastTime(ctx, chainID) require.NoError(t, expErr) @@ -978,7 +947,6 @@ func TestInMemoryStore_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { }) t.Run("wrong chain ID", func(t *testing.T) { wrongChainID := big.NewInt(999) - ctx := testutils.Context(t) expBroadcastAt, expErr := persistentStore.FindEarliestUnconfirmedBroadcastTime(ctx, wrongChainID) actBroadcastAt, actErr := inMemoryStore.FindEarliestUnconfirmedBroadcastTime(ctx, wrongChainID) require.NoError(t, expErr) @@ -1001,7 +969,7 @@ func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -1013,7 +981,6 @@ func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { require.NoError(t, err) t.Run("no results", func(t *testing.T) { - ctx := testutils.Context(t) expBlock, expErr := persistentStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, chainID) actBlock, actErr := inMemoryStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, chainID) require.NoError(t, expErr) @@ -1034,7 +1001,6 @@ func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) expBlock, expErr := persistentStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, chainID) actBlock, actErr := inMemoryStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, chainID) require.NoError(t, expErr) @@ -1046,7 +1012,6 @@ func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { t.Run("wrong chain ID", func(t *testing.T) { wrongChainID := big.NewInt(999) - ctx := testutils.Context(t) expBlock, expErr := persistentStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, wrongChainID) actBlock, actErr := inMemoryStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, wrongChainID) require.NoError(t, expErr) @@ -1068,7 +1033,7 @@ func TestInMemoryStore_LoadTxAttempts(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -1085,7 +1050,6 @@ func TestInMemoryStore_LoadTxAttempts(t *testing.T) { // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) expTx := evmtxmgr.Tx{ID: inTx.ID, TxAttempts: []evmtxmgr.TxAttempt{}, FromAddress: fromAddress} // empty tx attempts for test expErr := persistentStore.LoadTxAttempts(ctx, &expTx) require.Equal(t, 1, len(expTx.TxAttempts)) @@ -1114,7 +1078,7 @@ func TestInMemoryStore_PreloadTxes(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -1131,7 +1095,6 @@ func TestInMemoryStore_PreloadTxes(t *testing.T) { // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) expAttempts := []evmtxmgr.TxAttempt{{ID: 0, TxID: inTx.ID}} expErr := persistentStore.PreloadTxes(ctx, expAttempts) require.Equal(t, 1, len(expAttempts)) @@ -1160,7 +1123,7 @@ func TestInMemoryStore_IsTxFinalized(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -1179,7 +1142,6 @@ func TestInMemoryStore_IsTxFinalized(t *testing.T) { inTx.TxAttempts[0].Receipts = append(inTx.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec)) require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) blockHeight := int64(2) expIsFinalized, expErr := persistentStore.IsTxFinalized(ctx, blockHeight, inTx.ID, chainID) actIsFinalized, actErr := inMemoryStore.IsTxFinalized(ctx, blockHeight, inTx.ID, chainID) @@ -1196,7 +1158,6 @@ func TestInMemoryStore_IsTxFinalized(t *testing.T) { inTx.TxAttempts[0].Receipts = append(inTx.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec)) require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) blockHeight := int64(10) expIsFinalized, expErr := persistentStore.IsTxFinalized(ctx, blockHeight, inTx.ID, chainID) actIsFinalized, actErr := inMemoryStore.IsTxFinalized(ctx, blockHeight, inTx.ID, chainID) @@ -1213,7 +1174,6 @@ func TestInMemoryStore_IsTxFinalized(t *testing.T) { inTx.TxAttempts[0].Receipts = append(inTx.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec)) require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - ctx := testutils.Context(t) blockHeight := int64(10) wrongChainID := big.NewInt(999) expIsFinalized, expErr := persistentStore.IsTxFinalized(ctx, blockHeight, inTx.ID, wrongChainID) @@ -1236,7 +1196,7 @@ func TestInMemoryStore_FindTxsRequiringGasBump(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.TestSugared(t) chainID := ethClient.ConfiguredChainID() - ctx := context.Background() + ctx := testutils.Context(t) inMemoryStore, err := commontxmgr.NewInMemoryStore[ *big.Int, @@ -1252,9 +1212,9 @@ func TestInMemoryStore_FindTxsRequiringGasBump(t *testing.T) { // insert the transaction into the persistent store inTx_0 := mustInsertUnconfirmedEthTxWithAttemptState(t, persistentStore, 1, fromAddress, txmgrtypes.TxAttemptBroadcast) - require.NoError(t, persistentStore.SetBroadcastBeforeBlockNum(testutils.Context(t), currentBlockNum, chainID)) + require.NoError(t, persistentStore.SetBroadcastBeforeBlockNum(ctx, currentBlockNum, chainID)) inTx_1 := mustInsertUnconfirmedEthTxWithAttemptState(t, persistentStore, 2, fromAddress, txmgrtypes.TxAttemptBroadcast) - require.NoError(t, persistentStore.SetBroadcastBeforeBlockNum(testutils.Context(t), currentBlockNum+1, chainID)) + require.NoError(t, persistentStore.SetBroadcastBeforeBlockNum(ctx, currentBlockNum+1, chainID)) // insert the transaction into the in-memory store inTx_0.TxAttempts[0].BroadcastBeforeBlockNum = ¤tBlockNum require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) @@ -1262,7 +1222,6 @@ func TestInMemoryStore_FindTxsRequiringGasBump(t *testing.T) { inTx_1.TxAttempts[0].BroadcastBeforeBlockNum = &tempCurrentBlockNum require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_1)) - ctx := testutils.Context(t) newBlock := int64(12) gasBumpThreshold := int64(2) expTxs, expErr := persistentStore.FindTxsRequiringGasBump(ctx, fromAddress, newBlock, gasBumpThreshold, 0, chainID) From 29834c9a2c4c711e78f45ae09c89e7691acb91fa Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 22 Mar 2024 14:05:42 -0400 Subject: [PATCH 66/84] implement tests for FindTxesByMetaFieldAndStates --- common/txmgr/inmemory_store.go | 84 ++++++++++++++----- .../evm/txmgr/evm_inmemory_store_test.go | 80 ++++++++++++++++++ 2 files changed, 145 insertions(+), 19 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index aa753d47430..5a6eccd6b00 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -9,6 +9,7 @@ import ( "math/big" "slices" "sort" + "strconv" "sync" "time" @@ -427,7 +428,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT error, ) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("find_txes_by_meta_field_and_states: %w", ErrInvalidChainID) + panic("invalid chain ID") } filterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { @@ -438,33 +439,21 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT if err := json.Unmarshal(json.RawMessage(*tx.Meta), &meta); err != nil { return false } - if v, ok := meta[metaField].(string); ok { - return v == metaValue - } - - return false + return isMetaValueEqual(meta[metaField], metaValue) } - txsLock := sync.Mutex{} txs := []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} - wg := sync.WaitGroup{} ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() for _, as := range ms.addressStates { - wg.Add(1) - go func(as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { - for _, tx := range as.findTxs(states, filterFn) { - etx := ms.deepCopyTx(tx) - txsLock.Lock() - txs = append(txs, etx) - txsLock.Unlock() - } - wg.Done() - }(as) + for _, tx := range as.findTxs(states, filterFn) { + etx := ms.deepCopyTx(tx) + txs = append(txs, etx) + } } - wg.Wait() return txs, nil } + func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []txmgrtypes.TxState, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { if ms.chainID.String() != chainID.String() { return nil, fmt.Errorf("find_txes_with_meta_field_by_states: %w", ErrInvalidChainID) @@ -1171,3 +1160,60 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) deepC return copyAttempt } + +func isMetaValueEqual(v interface{}, metaValue string) bool { + switch v := v.(type) { + case string: + return v == metaValue + case int: + o, err := strconv.ParseInt(metaValue, 10, 64) + if err != nil { + return false + } + return v == int(o) + case uint32: + o, err := strconv.ParseUint(metaValue, 10, 32) + if err != nil { + return false + } + return v == uint32(o) + case uint64: + o, err := strconv.ParseUint(metaValue, 10, 64) + if err != nil { + return false + } + return v == o + case int32: + o, err := strconv.ParseInt(metaValue, 10, 32) + if err != nil { + return false + } + return v == int32(o) + case int64: + o, err := strconv.ParseInt(metaValue, 10, 64) + if err != nil { + return false + } + return v == o + case float32: + o, err := strconv.ParseFloat(metaValue, 32) + if err != nil { + return false + } + return v == float32(o) + case float64: + o, err := strconv.ParseFloat(metaValue, 64) + if err != nil { + return false + } + return v == o + case bool: + o, err := strconv.ParseBool(metaValue) + if err != nil { + return false + } + return v == o + } + + return false +} diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index eef3308127e..1e897477f4e 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -1,6 +1,7 @@ package txmgr_test import ( + "encoding/json" "fmt" "math/big" "testing" @@ -11,11 +12,13 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" @@ -25,6 +28,83 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_FindTxesByMetaFieldAndStates(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := testutils.Context(t) + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + // initialize the Meta field which is sqlutil.JSON + subID := uint64(123) + b, err := json.Marshal(txmgr.TxMeta{SubID: &subID}) + require.NoError(t, err) + meta := sqlutil.JSON(b) + // initialize transactions + inTx_0 := cltest.NewEthTx(fromAddress) + inTx_0.Meta = &meta + // insert the transaction into the persistent store + require.NoError(t, persistentStore.InsertTx(&inTx_0)) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) + + tcs := []struct { + name string + inMetaField string + inMetaValue string + inStates []txmgrtypes.TxState + inChainID *big.Int + + hasErr bool + hasTxs bool + }{ + {"successfully finds tx", "SubId", "123", []txmgrtypes.TxState{commontxmgr.TxUnstarted}, chainID, false, true}, + {"incorrect state: finds no txs", "SubId", "123", []txmgrtypes.TxState{commontxmgr.TxConfirmed}, chainID, false, false}, + {"incorrect meta_value: finds no txs", "SubId", "incorrect", []txmgrtypes.TxState{commontxmgr.TxUnstarted}, chainID, false, false}, + {"unknown meta_field: finds no txs", "unknown", "123", []txmgrtypes.TxState{commontxmgr.TxUnstarted}, chainID, false, false}, + {"incorrect meta_field: finds no txs", "JobID", "123", []txmgrtypes.TxState{commontxmgr.TxUnstarted}, chainID, false, false}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + actTxs, actErr := inMemoryStore.FindTxesByMetaFieldAndStates(ctx, tc.inMetaField, tc.inMetaValue, tc.inStates, tc.inChainID) + expTxs, expErr := persistentStore.FindTxesByMetaFieldAndStates(ctx, tc.inMetaField, tc.inMetaValue, tc.inStates, tc.inChainID) + require.Equal(t, expErr, actErr) + if !tc.hasErr { + require.Nil(t, expErr) + require.Nil(t, actErr) + } + if tc.hasTxs { + require.NotEqual(t, 0, len(expTxs)) + assert.NotEqual(t, 0, len(actTxs)) + require.Equal(t, len(expTxs), len(actTxs)) + for i := 0; i < len(expTxs); i++ { + assertTxEqual(t, *expTxs[i], *actTxs[i]) + } + } else { + require.Equal(t, 0, len(expTxs)) + require.Equal(t, 0, len(actTxs)) + } + }) + } +} + func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { t.Parallel() From 46cf23e2b4fad074d58924e7bb1ae915fefc970a Mon Sep 17 00:00:00 2001 From: James Walker Date: Sat, 23 Mar 2024 19:13:12 -0400 Subject: [PATCH 67/84] implement tests for FindTxesWithMetaFieldByStates --- common/txmgr/inmemory_store.go | 17 +---- .../evm/txmgr/evm_inmemory_store_test.go | 75 +++++++++++++++++++ 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 5a6eccd6b00..6b95c1d732e 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -474,24 +474,15 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT return false } - txsLock := sync.Mutex{} txs := []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} - wg := sync.WaitGroup{} ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() for _, as := range ms.addressStates { - wg.Add(1) - go func(as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { - for _, tx := range as.findTxs(states, filterFn) { - etx := ms.deepCopyTx(tx) - txsLock.Lock() - txs = append(txs, etx) - txsLock.Unlock() - } - wg.Done() - }(as) + for _, tx := range as.findTxs(states, filterFn) { + etx := ms.deepCopyTx(tx) + txs = append(txs, etx) + } } - wg.Wait() return txs, nil } diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 1e897477f4e..3e1301d3aae 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -28,6 +28,81 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_FindTxesWithMetaFieldByStates(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := testutils.Context(t) + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + // initialize the Meta field which is sqlutil.JSON + subID := uint64(123) + b, err := json.Marshal(txmgr.TxMeta{SubID: &subID}) + require.NoError(t, err) + meta := sqlutil.JSON(b) + // initialize transactions + inTx_0 := cltest.NewEthTx(fromAddress) + inTx_0.Meta = &meta + // insert the transaction into the persistent store + require.NoError(t, persistentStore.InsertTx(&inTx_0)) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) + + tcs := []struct { + name string + inMetaField string + inStates []txmgrtypes.TxState + inChainID *big.Int + + hasErr bool + hasTxs bool + }{ + {"successfully finds tx", "SubId", []txmgrtypes.TxState{commontxmgr.TxUnstarted}, chainID, false, true}, + {"incorrect state: finds no txs", "SubId", []txmgrtypes.TxState{commontxmgr.TxConfirmed}, chainID, false, false}, + {"unknown meta_field: finds no txs", "unknown", []txmgrtypes.TxState{commontxmgr.TxUnstarted}, chainID, false, false}, + {"incorrect meta_field: finds no txs", "MaxLink", []txmgrtypes.TxState{commontxmgr.TxUnstarted}, chainID, false, false}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + actTxs, actErr := inMemoryStore.FindTxesWithMetaFieldByStates(ctx, tc.inMetaField, tc.inStates, tc.inChainID) + expTxs, expErr := persistentStore.FindTxesWithMetaFieldByStates(ctx, tc.inMetaField, tc.inStates, tc.inChainID) + require.Equal(t, expErr, actErr) + if !tc.hasErr { + require.Nil(t, expErr) + require.Nil(t, actErr) + } + if tc.hasTxs { + require.NotEqual(t, 0, len(expTxs)) + assert.NotEqual(t, 0, len(actTxs)) + require.Equal(t, len(expTxs), len(actTxs)) + for i := 0; i < len(expTxs); i++ { + assertTxEqual(t, *expTxs[i], *actTxs[i]) + } + } else { + require.Equal(t, 0, len(expTxs)) + require.Equal(t, 0, len(actTxs)) + } + }) + } +} + func TestInMemoryStore_FindTxesByMetaFieldAndStates(t *testing.T) { t.Parallel() From e8804c9538cb315792ff7252e45d9f439ba6bb94 Mon Sep 17 00:00:00 2001 From: James Walker Date: Sun, 24 Mar 2024 18:28:22 -0400 Subject: [PATCH 68/84] implement test for FindTxesWithMetaFieldByReceiptBlockNum --- common/txmgr/inmemory_store.go | 25 ++--- .../evm/txmgr/evm_inmemory_store_test.go | 106 +++++++++++++++++- 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 6b95c1d732e..daf6a376c10 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -456,7 +456,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithMetaFieldByStates(ctx context.Context, metaField string, states []txmgrtypes.TxState, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("find_txes_with_meta_field_by_states: %w", ErrInvalidChainID) + panic("invalid chain ID") } filterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { @@ -489,7 +489,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesWithMetaFieldByReceiptBlockNum(ctx context.Context, metaField string, blockNum int64, chainID *big.Int) ([]*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("find_txes_with_meta_field_by_receipt_block_num: %w", ErrInvalidChainID) + panic("invalid chain ID") } filterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { @@ -503,12 +503,12 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT if _, ok := meta[metaField]; !ok { return false } - if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + if len(tx.TxAttempts) == 0 { return false } for _, attempt := range tx.TxAttempts { - if attempt.Receipts == nil || len(attempt.Receipts) == 0 { + if len(attempt.Receipts) == 0 { continue } if attempt.Receipts[0].GetBlockNumber() == nil { @@ -520,24 +520,15 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT return false } - txsLock := sync.Mutex{} txs := []*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} - wg := sync.WaitGroup{} ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() for _, as := range ms.addressStates { - wg.Add(1) - go func(as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) { - for _, tx := range as.findTxs(nil, filterFn) { - etx := ms.deepCopyTx(tx) - txsLock.Lock() - txs = append(txs, etx) - txsLock.Unlock() - } - wg.Done() - }(as) + for _, tx := range as.findTxs(nil, filterFn) { + etx := ms.deepCopyTx(tx) + txs = append(txs, etx) + } } - wg.Wait() return txs, nil } diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 3e1301d3aae..8e2de086fd4 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -2,7 +2,6 @@ package txmgr_test import ( "encoding/json" - "fmt" "math/big" "testing" "time" @@ -28,6 +27,101 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_FindTxesWithMetaFieldByReceiptBlockNum(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := testutils.Context(t) + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + // initialize the Meta field which is sqlutil.JSON + subID := uint64(123) + b, err := json.Marshal(txmgr.TxMeta{SubID: &subID}) + require.NoError(t, err) + meta := sqlutil.JSON(b) + timeNow := time.Now() + nonce := evmtypes.Nonce(123) + blockNum := int64(3) + broadcastBeforeBlockNum := int64(3) + // initialize transactions + inTx_0 := cltest.NewEthTx(fromAddress) + inTx_0.BroadcastAt = &timeNow + inTx_0.InitialBroadcastAt = &timeNow + inTx_0.Sequence = &nonce + inTx_0.State = commontxmgr.TxConfirmed + inTx_0.MinConfirmations.SetValid(6) + inTx_0.Meta = &meta + // insert the transaction into the persistent store + require.NoError(t, persistentStore.InsertTx(&inTx_0)) + attempt := cltest.NewLegacyEthTxAttempt(t, inTx_0.ID) + attempt.BroadcastBeforeBlockNum = &broadcastBeforeBlockNum + attempt.State = txmgrtypes.TxAttemptBroadcast + require.NoError(t, persistentStore.InsertTxAttempt(&attempt)) + inTx_0.TxAttempts = append(inTx_0.TxAttempts, attempt) + // insert the transaction receipt into the persistent store + rec_0 := mustInsertEthReceipt(t, persistentStore, 3, utils.NewHash(), inTx_0.TxAttempts[0].Hash) + inTx_0.TxAttempts[0].Receipts = append(inTx_0.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec_0)) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) + + tcs := []struct { + name string + inMetaField string + inBlockNum int64 + inChainID *big.Int + + hasErr bool + hasTxs bool + }{ + {"successfully finds tx", "SubId", blockNum, chainID, false, true}, + {"unknown meta_field: finds no txs", "unknown", blockNum, chainID, false, false}, + {"incorrect meta_field: finds no txs", "MaxLink", blockNum, chainID, false, false}, + {"incorrect blockNum: finds no txs", "SubId", 12, chainID, false, false}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + actTxs, actErr := inMemoryStore.FindTxesWithMetaFieldByReceiptBlockNum(ctx, tc.inMetaField, tc.inBlockNum, tc.inChainID) + expTxs, expErr := persistentStore.FindTxesWithMetaFieldByReceiptBlockNum(ctx, tc.inMetaField, tc.inBlockNum, tc.inChainID) + require.Equal(t, expErr, actErr) + if tc.hasErr { + require.NotNil(t, expErr) + require.NotNil(t, actErr) + } else { + require.Nil(t, expErr) + require.Nil(t, actErr) + } + if tc.hasTxs { + require.NotEqual(t, 0, len(expTxs)) + assert.NotEqual(t, 0, len(actTxs)) + require.Equal(t, len(expTxs), len(actTxs)) + for i := 0; i < len(expTxs); i++ { + assertTxEqual(t, *expTxs[i], *actTxs[i]) + } + } else { + require.Equal(t, 0, len(expTxs)) + require.Equal(t, 0, len(actTxs)) + } + }) + } +} + func TestInMemoryStore_FindTxesWithMetaFieldByStates(t *testing.T) { t.Parallel() @@ -1382,10 +1476,6 @@ func TestInMemoryStore_FindTxsRequiringGasBump(t *testing.T) { expTxs, expErr := persistentStore.FindTxsRequiringGasBump(ctx, fromAddress, newBlock, gasBumpThreshold, 0, chainID) actTxs, actErr := inMemoryStore.FindTxsRequiringGasBump(ctx, fromAddress, newBlock, gasBumpThreshold, 0, chainID) require.NoError(t, expErr) - fmt.Println("EXPTX", expTxs[0].ID) - for i := 0; i < len(expTxs[0].TxAttempts); i++ { - fmt.Println("EXPTX_ATTEMPT", expTxs[0].TxAttempts[i].ID) - } require.NoError(t, actErr) require.Equal(t, len(expTxs), len(actTxs)) for i := 0; i < len(expTxs); i++ { @@ -1428,6 +1518,9 @@ func assertTxEqual(t *testing.T, exp, act evmtxmgr.Tx) { assert.Equal(t, exp.SignalCallback, act.SignalCallback) assert.Equal(t, exp.CallbackCompleted, act.CallbackCompleted) + if len(exp.TxAttempts) == 0 { + return + } require.Equal(t, len(exp.TxAttempts), len(act.TxAttempts)) for i := 0; i < len(exp.TxAttempts); i++ { assertTxAttemptEqual(t, exp.TxAttempts[i], act.TxAttempts[i]) @@ -1446,6 +1539,9 @@ func assertTxAttemptEqual(t *testing.T, exp, act evmtxmgr.TxAttempt) { assert.Equal(t, exp.State, act.State) assert.Equal(t, exp.TxType, act.TxType) + if len(exp.Receipts) == 0 { + return + } require.Equal(t, len(exp.Receipts), len(act.Receipts)) for i := 0; i < len(exp.Receipts); i++ { assertChainReceiptEqual(t, exp.Receipts[i], act.Receipts[i]) From cdb374a925803591435443f2aecb42e44c93d88e Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 25 Mar 2024 10:22:21 -0400 Subject: [PATCH 69/84] implement tests for FindTxAttemptsRequiringResend --- common/txmgr/inmemory_store.go | 18 ++-- .../evm/txmgr/evm_inmemory_store_test.go | 102 ++++++++++++++++++ 2 files changed, 114 insertions(+), 6 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index daf6a376c10..72379c269eb 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -640,18 +640,18 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxAttemptsRequiringResend(_ context.Context, olderThan time.Time, maxInFlightTransactions uint32, chainID CHAIN_ID, address ADDR) ([]txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("find_tx_attempts_requiring_resend: %w", ErrInvalidChainID) + panic("invalid chain ID") } ms.addressStatesLock.RLock() defer ms.addressStatesLock.RUnlock() as, ok := ms.addressStates[address] if !ok { - return nil, fmt.Errorf("find_tx_attempts_requiring_resend: %w", ErrAddressNotFound) + return nil, nil } txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + if len(tx.TxAttempts) == 0 { return false } return tx.BroadcastAt.Before(olderThan) || tx.BroadcastAt.Equal(olderThan) @@ -662,11 +662,17 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT states := []txmgrtypes.TxState{TxUnconfirmed, TxConfirmedMissingReceipt} attempts := as.findTxAttempts(states, txFilter, txAttemptFilter) // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC - sort.Slice(attempts, func(i, j int) bool { - return (*attempts[i].Tx.Sequence).Int64() < (*attempts[j].Tx.Sequence).Int64() + slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { + aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence + if aSequence == nil || bSequence == nil { + return 0 + } + // TODO(jtw): figure out how to get gas price and gas tip cap from TxFee + + return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) }) // LIMIT by maxInFlightTransactions - if len(attempts) > int(maxInFlightTransactions) { + if maxInFlightTransactions > 0 && len(attempts) > int(maxInFlightTransactions) { attempts = attempts[:maxInFlightTransactions] } diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 8e2de086fd4..ee56a7bdb63 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -16,6 +16,7 @@ import ( txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" @@ -27,6 +28,107 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) +func TestInMemoryStore_FindTxAttemptsRequiringResend(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := testutils.Context(t) + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + // insert the transaction into the persistent store + inTx_1 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, persistentStore, 1, fromAddress, time.Unix(1616509200, 0)) + inTx_3 := mustInsertUnconfirmedEthTxWithBroadcastDynamicFeeAttempt(t, persistentStore, 3, fromAddress, time.Unix(1616509400, 0)) + inTx_0 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, persistentStore, 0, fromAddress, time.Unix(1616509100, 0)) + inTx_2 := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, persistentStore, 2, fromAddress, time.Unix(1616509300, 0)) + // modify the attempts + attempt0_2 := newBroadcastLegacyEthTxAttempt(t, inTx_0.ID) + attempt0_2.TxFee = gas.EvmFee{Legacy: assets.NewWeiI(10)} + require.NoError(t, persistentStore.InsertTxAttempt(&attempt0_2)) + + attempt2_2 := newInProgressLegacyEthTxAttempt(t, inTx_2.ID) + attempt2_2.TxFee = gas.EvmFee{Legacy: assets.NewWeiI(10)} + require.NoError(t, persistentStore.InsertTxAttempt(&attempt2_2)) + + attempt3_2 := cltest.NewDynamicFeeEthTxAttempt(t, inTx_3.ID) + attempt3_2.TxFee.DynamicTipCap = assets.NewWeiI(10) + attempt3_2.TxFee.DynamicFeeCap = assets.NewWeiI(20) + attempt3_2.State = txmgrtypes.TxAttemptBroadcast + require.NoError(t, persistentStore.InsertTxAttempt(&attempt3_2)) + attempt3_4 := cltest.NewDynamicFeeEthTxAttempt(t, inTx_3.ID) + attempt3_4.TxFee.DynamicTipCap = assets.NewWeiI(30) + attempt3_4.TxFee.DynamicFeeCap = assets.NewWeiI(40) + attempt3_4.State = txmgrtypes.TxAttemptBroadcast + require.NoError(t, persistentStore.InsertTxAttempt(&attempt3_4)) + attempt3_3 := cltest.NewDynamicFeeEthTxAttempt(t, inTx_3.ID) + attempt3_3.TxFee.DynamicTipCap = assets.NewWeiI(20) + attempt3_3.TxFee.DynamicFeeCap = assets.NewWeiI(30) + attempt3_3.State = txmgrtypes.TxAttemptBroadcast + require.NoError(t, persistentStore.InsertTxAttempt(&attempt3_3)) + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_1)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_2)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_3)) + + tcs := []struct { + name string + inOlderThan time.Time + inMaxInFlightTransactions uint32 + inChainID *big.Int + inFromAddress common.Address + + hasErr bool + hasTxAttempts bool + }{ + //{"finds nothing if transactions from a different key", time.Now(), 10, chainID, utils.RandomAddress(), false, false}, + //{"returns the highest price attempt for each transaction that was last broadcast before or on the given time", time.Unix(1616509200, 0), 0, chainID, fromAddress, false, true}, + //{"returns the highest price attempt for EIP-1559 transactions", time.Unix(1616509400, 0), 0, chainID, fromAddress, false, true}, + {"applies limit", time.Unix(1616509200, 0), 1, chainID, fromAddress, false, true}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + actTxAttempts, actErr := inMemoryStore.FindTxAttemptsRequiringResend(ctx, tc.inOlderThan, tc.inMaxInFlightTransactions, tc.inChainID, tc.inFromAddress) + expTxAttempts, expErr := persistentStore.FindTxAttemptsRequiringResend(ctx, tc.inOlderThan, tc.inMaxInFlightTransactions, tc.inChainID, tc.inFromAddress) + require.Equal(t, expErr, actErr) + if tc.hasErr { + require.NotNil(t, expErr) + require.NotNil(t, actErr) + } else { + require.Nil(t, expErr) + require.Nil(t, actErr) + } + if tc.hasTxAttempts { + require.NotEqual(t, 0, len(expTxAttempts)) + assert.NotEqual(t, 0, len(actTxAttempts)) + require.Equal(t, len(expTxAttempts), len(actTxAttempts)) + for i := 0; i < len(expTxAttempts); i++ { + assertTxAttemptEqual(t, expTxAttempts[i], actTxAttempts[i]) + } + } else { + require.Equal(t, 0, len(expTxAttempts)) + require.Equal(t, 0, len(actTxAttempts)) + } + }) + } +} + func TestInMemoryStore_FindTxesWithMetaFieldByReceiptBlockNum(t *testing.T) { t.Parallel() From 89b8f22a9601cec83e9b9676fb1812ce584ea05d Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 25 Mar 2024 10:49:08 -0400 Subject: [PATCH 70/84] add comment about figuring out sorting issues --- common/txmgr/inmemory_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 8c0aea89e87..6ee1958cd3a 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -674,7 +674,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT if aSequence == nil || bSequence == nil { return 0 } - // TODO(jtw): figure out how to get gas price and gas tip cap from TxFee + // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) }) From 6609492573c8dad397434a04a7df1b95462be3f4 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 25 Mar 2024 11:10:42 -0400 Subject: [PATCH 71/84] clean up merge issues --- common/txmgr/inmemory_store.go | 28 ++++--- .../evm/txmgr/evm_inmemory_store_test.go | 76 +++++++++---------- 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 6ee1958cd3a..ac70c9ce051 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -370,23 +370,24 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT error, ) { if ms.chainID.String() != chainID.String() { - return nil, fmt.Errorf("find_txes_pending_callback: %w", ErrInvalidChainID) + panic("invalid chain ID") } filterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - if tx.TxAttempts == nil || len(tx.TxAttempts) == 0 { + if len(tx.TxAttempts) == 0 { return false } - // TODO: loop through all attempts since any of them can have a receipt - if tx.TxAttempts[0].Receipts == nil || len(tx.TxAttempts[0].Receipts) == 0 { - return false - } + for i := 0; i < len(tx.TxAttempts); i++ { + if len(tx.TxAttempts[i].Receipts) == 0 { + continue + } - if tx.PipelineTaskRunID.Valid && tx.SignalCallback && !tx.CallbackCompleted && - tx.TxAttempts[0].Receipts[0].GetBlockNumber() != nil && - big.NewInt(blockNum-int64(tx.MinConfirmations.Uint32)).Cmp(tx.TxAttempts[0].Receipts[0].GetBlockNumber()) > 0 { - return true + if tx.PipelineTaskRunID.Valid && tx.SignalCallback && !tx.CallbackCompleted && + tx.TxAttempts[i].Receipts[0].GetBlockNumber() != nil && + big.NewInt(blockNum-int64(tx.MinConfirmations.Uint32)).Cmp(tx.TxAttempts[i].Receipts[0].GetBlockNumber()) > 0 { + return true + } } return false @@ -675,6 +676,13 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT return 0 } // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee + /* + v, ok := a.TxFee.(*gas.EvmFee) + if !ok { + panic("invalid gas fee") + } + fmt.Println("hereh", v) + */ return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) }) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index ee56a7bdb63..7af395d8c7b 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -33,7 +33,7 @@ func TestInMemoryStore_FindTxAttemptsRequiringResend(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -59,27 +59,27 @@ func TestInMemoryStore_FindTxAttemptsRequiringResend(t *testing.T) { // modify the attempts attempt0_2 := newBroadcastLegacyEthTxAttempt(t, inTx_0.ID) attempt0_2.TxFee = gas.EvmFee{Legacy: assets.NewWeiI(10)} - require.NoError(t, persistentStore.InsertTxAttempt(&attempt0_2)) + require.NoError(t, persistentStore.InsertTxAttempt(ctx, &attempt0_2)) attempt2_2 := newInProgressLegacyEthTxAttempt(t, inTx_2.ID) attempt2_2.TxFee = gas.EvmFee{Legacy: assets.NewWeiI(10)} - require.NoError(t, persistentStore.InsertTxAttempt(&attempt2_2)) + require.NoError(t, persistentStore.InsertTxAttempt(ctx, &attempt2_2)) attempt3_2 := cltest.NewDynamicFeeEthTxAttempt(t, inTx_3.ID) attempt3_2.TxFee.DynamicTipCap = assets.NewWeiI(10) attempt3_2.TxFee.DynamicFeeCap = assets.NewWeiI(20) attempt3_2.State = txmgrtypes.TxAttemptBroadcast - require.NoError(t, persistentStore.InsertTxAttempt(&attempt3_2)) + require.NoError(t, persistentStore.InsertTxAttempt(ctx, &attempt3_2)) attempt3_4 := cltest.NewDynamicFeeEthTxAttempt(t, inTx_3.ID) attempt3_4.TxFee.DynamicTipCap = assets.NewWeiI(30) attempt3_4.TxFee.DynamicFeeCap = assets.NewWeiI(40) attempt3_4.State = txmgrtypes.TxAttemptBroadcast - require.NoError(t, persistentStore.InsertTxAttempt(&attempt3_4)) + require.NoError(t, persistentStore.InsertTxAttempt(ctx, &attempt3_4)) attempt3_3 := cltest.NewDynamicFeeEthTxAttempt(t, inTx_3.ID) attempt3_3.TxFee.DynamicTipCap = assets.NewWeiI(20) attempt3_3.TxFee.DynamicFeeCap = assets.NewWeiI(30) attempt3_3.State = txmgrtypes.TxAttemptBroadcast - require.NoError(t, persistentStore.InsertTxAttempt(&attempt3_3)) + require.NoError(t, persistentStore.InsertTxAttempt(ctx, &attempt3_3)) // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_1)) @@ -134,7 +134,7 @@ func TestInMemoryStore_FindTxesWithMetaFieldByReceiptBlockNum(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -170,11 +170,11 @@ func TestInMemoryStore_FindTxesWithMetaFieldByReceiptBlockNum(t *testing.T) { inTx_0.MinConfirmations.SetValid(6) inTx_0.Meta = &meta // insert the transaction into the persistent store - require.NoError(t, persistentStore.InsertTx(&inTx_0)) + require.NoError(t, persistentStore.InsertTx(ctx, &inTx_0)) attempt := cltest.NewLegacyEthTxAttempt(t, inTx_0.ID) attempt.BroadcastBeforeBlockNum = &broadcastBeforeBlockNum attempt.State = txmgrtypes.TxAttemptBroadcast - require.NoError(t, persistentStore.InsertTxAttempt(&attempt)) + require.NoError(t, persistentStore.InsertTxAttempt(ctx, &attempt)) inTx_0.TxAttempts = append(inTx_0.TxAttempts, attempt) // insert the transaction receipt into the persistent store rec_0 := mustInsertEthReceipt(t, persistentStore, 3, utils.NewHash(), inTx_0.TxAttempts[0].Hash) @@ -229,7 +229,7 @@ func TestInMemoryStore_FindTxesWithMetaFieldByStates(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -256,7 +256,7 @@ func TestInMemoryStore_FindTxesWithMetaFieldByStates(t *testing.T) { inTx_0 := cltest.NewEthTx(fromAddress) inTx_0.Meta = &meta // insert the transaction into the persistent store - require.NoError(t, persistentStore.InsertTx(&inTx_0)) + require.NoError(t, persistentStore.InsertTx(ctx, &inTx_0)) // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) @@ -304,7 +304,7 @@ func TestInMemoryStore_FindTxesByMetaFieldAndStates(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -331,7 +331,7 @@ func TestInMemoryStore_FindTxesByMetaFieldAndStates(t *testing.T) { inTx_0 := cltest.NewEthTx(fromAddress) inTx_0.Meta = &meta // insert the transaction into the persistent store - require.NoError(t, persistentStore.InsertTx(&inTx_0)) + require.NoError(t, persistentStore.InsertTx(ctx, &inTx_0)) // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) @@ -381,7 +381,7 @@ func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -403,7 +403,7 @@ func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { inTx := cltest.NewEthTx(fromAddress) inTx.IdempotencyKey = &idempotencyKey // insert the transaction into the persistent store - require.NoError(t, persistentStore.InsertTx(&inTx)) + require.NoError(t, persistentStore.InsertTx(ctx, &inTx)) // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) @@ -447,7 +447,7 @@ func TestInMemoryStore_CheckTxQueueCapacity(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -471,7 +471,7 @@ func TestInMemoryStore_CheckTxQueueCapacity(t *testing.T) { } for _, inTx := range inTxs { // insert the transaction into the persistent store - require.NoError(t, persistentStore.InsertTx(&inTx)) + require.NoError(t, persistentStore.InsertTx(ctx, &inTx)) // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) } @@ -512,7 +512,7 @@ func TestInMemoryStore_CountUnstartedTransactions(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -537,7 +537,7 @@ func TestInMemoryStore_CountUnstartedTransactions(t *testing.T) { } for _, inTx := range inUnstartedTxs { // insert the transaction into the persistent store - require.NoError(t, persistentStore.InsertTx(&inTx)) + require.NoError(t, persistentStore.InsertTx(ctx, &inTx)) // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) } @@ -577,7 +577,7 @@ func TestInMemoryStore_CountUnconfirmedTransactions(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -639,7 +639,7 @@ func TestInMemoryStore_FindTxAttemptsConfirmedMissingReceipt(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -713,7 +713,7 @@ func TestInMemoryStore_FindTxAttemptsRequiringReceiptFetch(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -787,7 +787,7 @@ func TestInMemoryStore_GetInProgressTxAttempts(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -835,7 +835,7 @@ func TestInMemoryStore_HasInProgressTransaction(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -880,7 +880,7 @@ func TestInMemoryStore_GetTxByID(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -928,7 +928,7 @@ func TestInMemoryStore_FindTxWithSequence(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -991,7 +991,7 @@ func TestInMemoryStore_CountTransactionsByState(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -1045,7 +1045,7 @@ func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testi db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) _, otherAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -1079,7 +1079,7 @@ func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testi attempt3_2 := cltest.NewLegacyEthTxAttempt(t, inTx_3.ID) attempt3_2.State = txmgrtypes.TxAttemptInsufficientFunds attempt3_2.TxFee.Legacy = assets.NewWeiI(100) - require.NoError(t, persistentStore.InsertTxAttempt(&attempt3_2)) + require.NoError(t, persistentStore.InsertTxAttempt(ctx, &attempt3_2)) inTx_1 := mustInsertUnconfirmedEthTxWithInsufficientEthAttempt(t, persistentStore, 0, fromAddress) // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_2)) @@ -1133,7 +1133,7 @@ func TestInMemoryStore_GetNonFatalTransactions(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -1193,7 +1193,7 @@ func TestInMemoryStore_FindTransactionsConfirmedInBlockRange(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -1256,7 +1256,7 @@ func TestInMemoryStore_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -1313,7 +1313,7 @@ func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -1347,7 +1347,7 @@ func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { attempt := cltest.NewLegacyEthTxAttempt(t, inTx.ID) attempt.BroadcastBeforeBlockNum = &broadcastBeforeBlockNum attempt.State = txmgrtypes.TxAttemptBroadcast - require.NoError(t, persistentStore.InsertTxAttempt(&attempt)) + require.NoError(t, persistentStore.InsertTxAttempt(ctx, &attempt)) inTx.TxAttempts = append(inTx.TxAttempts, attempt) // insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) @@ -1377,7 +1377,7 @@ func TestInMemoryStore_LoadTxAttempts(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -1422,7 +1422,7 @@ func TestInMemoryStore_PreloadTxes(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -1467,7 +1467,7 @@ func TestInMemoryStore_IsTxFinalized(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) @@ -1540,7 +1540,7 @@ func TestInMemoryStore_FindTxsRequiringGasBump(t *testing.T) { db := pgtest.NewSqlxDB(t) _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) - persistentStore := cltest.NewTestTxStore(t, db, dbcfg) + persistentStore := cltest.NewTestTxStore(t, db) kst := cltest.NewKeyStore(t, db, dbcfg) _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) From 163fb0e35fce77cca8a5763b249ca5c834db90c6 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 25 Mar 2024 13:39:02 -0400 Subject: [PATCH 72/84] implement tests for FindTxesPendingCallback --- common/txmgr/inmemory_store.go | 23 ++- common/txmgr/test_helpers.go | 4 +- .../evm/txmgr/evm_inmemory_store_test.go | 161 ++++++++++++++++++ 3 files changed, 179 insertions(+), 9 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index ac70c9ce051..3c720416a76 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -383,9 +383,13 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT continue } - if tx.PipelineTaskRunID.Valid && tx.SignalCallback && !tx.CallbackCompleted && - tx.TxAttempts[i].Receipts[0].GetBlockNumber() != nil && - big.NewInt(blockNum-int64(tx.MinConfirmations.Uint32)).Cmp(tx.TxAttempts[i].Receipts[0].GetBlockNumber()) > 0 { + if !tx.PipelineTaskRunID.Valid || !tx.SignalCallback || tx.CallbackCompleted { + continue + } + receipt := tx.TxAttempts[i].Receipts[0] + minConfirmations := int64(tx.MinConfirmations.Uint32) + if receipt.GetBlockNumber() != nil && + receipt.GetBlockNumber().Int64() <= (blockNum-minConfirmations) { return true } } @@ -412,10 +416,15 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT failOnRevert = v } - receiptsPlus[i] = txmgrtypes.ReceiptPlus[R]{ - ID: tx.PipelineTaskRunID.UUID, - Receipt: (tx.TxAttempts[0].Receipts[0]).(R), - FailOnRevert: failOnRevert, + for j := 0; j < len(tx.TxAttempts); j++ { + if len(tx.TxAttempts[j].Receipts) == 0 { + continue + } + receiptsPlus[i] = txmgrtypes.ReceiptPlus[R]{ + ID: tx.PipelineTaskRunID.UUID, + Receipt: tx.TxAttempts[j].Receipts[0].(R), + FailOnRevert: failOnRevert, + } } clear(meta) } diff --git a/common/txmgr/test_helpers.go b/common/txmgr/test_helpers.go index fa9af9a506a..153c3211442 100644 --- a/common/txmgr/test_helpers.go +++ b/common/txmgr/test_helpers.go @@ -2,7 +2,6 @@ package txmgr import ( "context" - "fmt" "time" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" @@ -54,7 +53,8 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) XXXTestAba func (b *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) XXXTestInsertTx(fromAddr ADDR, tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error { as, ok := b.addressStates[fromAddr] if !ok { - return fmt.Errorf("address not found: %s", fromAddr) + as = newAddressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE](b.lggr, b.chainID, fromAddr, 10, nil) + b.addressStates[fromAddr] = as } as.allTxs[tx.ID] = tx diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 7af395d8c7b..972094026f6 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -7,14 +7,17 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" + clnull "github.com/smartcontractkit/chainlink-common/pkg/utils/null" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" @@ -26,8 +29,166 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/evmtest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/services/pipeline" ) +func TestInMemoryStore_FindTxesPendingCallback(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := testutils.Context(t) + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + head := evmtypes.Head{ + Hash: utils.NewHash(), + Number: 10, + Parent: &evmtypes.Head{ + Hash: utils.NewHash(), + Number: 9, + Parent: &evmtypes.Head{ + Number: 8, + Hash: utils.NewHash(), + Parent: nil, + }, + }, + } + minConfirmations := int64(2) + + pgtest.MustExec(t, db, `SET CONSTRAINTS pipeline_runs_pipeline_spec_id_fkey DEFERRED`) + // insert the transaction into the persistent store + // Suspended run waiting for callback + run1 := cltest.MustInsertPipelineRun(t, db) + tr1 := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run1.ID) + pgtest.MustExec(t, db, `UPDATE pipeline_runs SET state = 'suspended' WHERE id = $1`, run1.ID) + inTx_0 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 3, 1, fromAddress) + pgtest.MustExec(t, db, `UPDATE evm.txes SET meta='{"FailOnRevert": true}'`) + attempt1 := inTx_0.TxAttempts[0] + r_0 := mustInsertEthReceipt(t, persistentStore, head.Number-minConfirmations, head.Hash, attempt1.Hash) + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE WHERE id = $3`, &tr1.ID, minConfirmations, inTx_0.ID) + failOnRevert := null.BoolFrom(true) + b, err := json.Marshal(txmgr.TxMeta{FailOnRevert: failOnRevert}) + require.NoError(t, err) + meta := sqlutil.JSON(b) + inTx_0.Meta = &meta + inTx_0.TxAttempts[0].Receipts = append(inTx_0.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&r_0)) + inTx_0.MinConfirmations = clnull.Uint32From(uint32(minConfirmations)) + inTx_0.PipelineTaskRunID = uuid.NullUUID{UUID: tr1.ID, Valid: true} + inTx_0.SignalCallback = true + + // Callback to pipeline service completed. Should be ignored + run2 := cltest.MustInsertPipelineRunWithStatus(t, db, 0, pipeline.RunStatusCompleted) + tr2 := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run2.ID) + inTx_1 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 4, 1, fromAddress) + pgtest.MustExec(t, db, `UPDATE evm.txes SET meta='{"FailOnRevert": false}'`) + attempt2 := inTx_1.TxAttempts[0] + r_1 := mustInsertEthReceipt(t, persistentStore, head.Number-minConfirmations, head.Hash, attempt2.Hash) + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE, callback_completed = TRUE WHERE id = $3`, &tr2.ID, minConfirmations, inTx_1.ID) + failOnRevert = null.BoolFrom(false) + b, err = json.Marshal(txmgr.TxMeta{FailOnRevert: failOnRevert}) + require.NoError(t, err) + meta = sqlutil.JSON(b) + inTx_1.Meta = &meta + inTx_1.TxAttempts[0].Receipts = append(inTx_1.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&r_1)) + inTx_1.MinConfirmations = clnull.Uint32From(uint32(minConfirmations)) + inTx_1.PipelineTaskRunID = uuid.NullUUID{UUID: tr2.ID, Valid: true} + inTx_1.SignalCallback = true + inTx_1.CallbackCompleted = true + + // Suspended run younger than minConfirmations. Should be ignored + run3 := cltest.MustInsertPipelineRun(t, db) + tr3 := cltest.MustInsertUnfinishedPipelineTaskRun(t, db, run3.ID) + pgtest.MustExec(t, db, `UPDATE pipeline_runs SET state = 'suspended' WHERE id = $1`, run3.ID) + inTx_2 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 5, 1, fromAddress) + pgtest.MustExec(t, db, `UPDATE evm.txes SET meta='{"FailOnRevert": false}'`) + attempt3 := inTx_2.TxAttempts[0] + r_2 := mustInsertEthReceipt(t, persistentStore, head.Number, head.Hash, attempt3.Hash) + pgtest.MustExec(t, db, `UPDATE evm.txes SET pipeline_task_run_id = $1, min_confirmations = $2, signal_callback = TRUE WHERE id = $3`, &tr3.ID, minConfirmations, inTx_2.ID) + failOnRevert = null.BoolFrom(false) + b, err = json.Marshal(txmgr.TxMeta{FailOnRevert: failOnRevert}) + require.NoError(t, err) + meta = sqlutil.JSON(b) + inTx_2.Meta = &meta + inTx_2.TxAttempts[0].Receipts = append(inTx_2.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&r_2)) + inTx_2.MinConfirmations = clnull.Uint32From(uint32(minConfirmations)) + inTx_2.PipelineTaskRunID = uuid.NullUUID{UUID: tr3.ID, Valid: true} + inTx_2.SignalCallback = true + + // Tx not marked for callback. Should be ignore + inTx_3 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 6, 1, fromAddress) + attempt4 := inTx_3.TxAttempts[0] + r_3 := mustInsertEthReceipt(t, persistentStore, head.Number, head.Hash, attempt4.Hash) + pgtest.MustExec(t, db, `UPDATE evm.txes SET min_confirmations = $1 WHERE id = $2`, minConfirmations, inTx_3.ID) + inTx_3.TxAttempts[0].Receipts = append(inTx_3.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&r_3)) + inTx_3.MinConfirmations = clnull.Uint32From(uint32(minConfirmations)) + + // Unconfirmed Tx without receipts. Should be ignored + inTx_4 := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 7, 1, fromAddress) + pgtest.MustExec(t, db, `UPDATE evm.txes SET min_confirmations = $1 WHERE id = $2`, minConfirmations, inTx_4.ID) + inTx_4.MinConfirmations = clnull.Uint32From(uint32(minConfirmations)) + + // insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_0)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_1)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_2)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_3)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx_4)) + + tcs := []struct { + name string + inHeadNumber int64 + inChainID *big.Int + + hasErr bool + hasReceipts bool + }{ + {"successfully finds receipts", head.Number, chainID, false, true}, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + actReceipts, actErr := inMemoryStore.FindTxesPendingCallback(ctx, tc.inHeadNumber, tc.inChainID) + expReceipts, expErr := persistentStore.FindTxesPendingCallback(ctx, tc.inHeadNumber, tc.inChainID) + require.Equal(t, expErr, actErr) + if tc.hasErr { + require.NotNil(t, expErr) + require.NotNil(t, actErr) + } else { + require.Nil(t, expErr) + require.Nil(t, actErr) + } + if tc.hasReceipts { + require.NotEqual(t, 0, len(expReceipts)) + assert.NotEqual(t, 0, len(actReceipts)) + require.Equal(t, len(expReceipts), len(actReceipts)) + for i := 0; i < len(expReceipts); i++ { + assert.Equal(t, expReceipts[i].ID, actReceipts[i].ID) + assert.Equal(t, expReceipts[i].FailOnRevert, actReceipts[i].FailOnRevert) + assertChainReceiptEqual(t, expReceipts[i].Receipt, actReceipts[i].Receipt) + } + } else { + require.Equal(t, 0, len(expReceipts)) + require.Equal(t, 0, len(actReceipts)) + } + }) + } +} + func TestInMemoryStore_FindTxAttemptsRequiringResend(t *testing.T) { t.Parallel() From 1430b7d944d78545b0c2a899c7d0d7bb9c94f44b Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 25 Mar 2024 13:41:13 -0400 Subject: [PATCH 73/84] remove initials from NOTE --- common/txmgr/inmemory_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 3c720416a76..b610f49e216 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -172,7 +172,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindL // CountUnconfirmedTransactions returns the number of unconfirmed transactions for a given address. // Unconfirmed transactions are transactions that have been broadcast but not confirmed on-chain. -// NOTE(jtw): used to calculate total inflight transactions +// NOTE: used to calculate total inflight transactions func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountUnconfirmedTransactions(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (uint32, error) { if ms.chainID.String() != chainID.String() { return 0, nil From ad2ae5d6c0ea60d41c405435b11cf8fa3f65a4a0 Mon Sep 17 00:00:00 2001 From: James Walker Date: Tue, 26 Mar 2024 16:59:46 -0400 Subject: [PATCH 74/84] fix loop iteration --- common/txmgr/inmemory_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index fff742901e9..e5fe28c5b77 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -366,7 +366,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) SaveS tx.BroadcastAt = &broadcastAt } - for i := range tx.TxAttempts { + for i := 0; i < len(tx.TxAttempts); i++ { if tx.TxAttempts[i].ID == attempt.ID { tx.TxAttempts[i].State = txmgrtypes.TxAttemptBroadcast return From 6920a21b2f6e66802a3ee96fb32dca1e86b81a51 Mon Sep 17 00:00:00 2001 From: Dylan Tinianov Date: Thu, 28 Mar 2024 13:27:45 -0400 Subject: [PATCH 75/84] Add enabledAddresses --- common/txmgr/inmemory_store.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 16d19c540e6..4c2e26413ef 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -81,11 +81,20 @@ func NewInMemoryStore[ ms.maxUnstarted = 10000 } + addressesToTxs := map[ADDR][]txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + // populate all enabled addresses + enabledAddresses, err := keyStore.EnabledAddressesForChain(ctx, chainID) + if err != nil { + return nil, fmt.Errorf("new_in_memory_store: %w", err) + } + for _, addr := range enabledAddresses { + addressesToTxs[addr] = []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + } + txs, err := persistentTxStore.GetAllTransactions(ctx, chainID) if err != nil { return nil, fmt.Errorf("address_state: initialization: %w", err) } - addressesToTxs := map[ADDR][]txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} for _, tx := range txs { at, exists := addressesToTxs[tx.FromAddress] if !exists { From a1997ad1f51e06c0c5fe50d3d11f68b6afd79109 Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 29 Mar 2024 15:21:27 -0400 Subject: [PATCH 76/84] fix merge issues --- .../evm/txmgr/evm_inmemory_store_test.go | 85 +++++++++++++------ 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 4ce330a9326..86234abe232 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -22,11 +22,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) -<<<<<<< HEAD func TestInMemoryStore_SaveInsufficientFundsAttempt(t *testing.T) { -======= -func TestInMemoryStore_SaveSentAttempt(t *testing.T) { ->>>>>>> jtw/step-3-01 t.Parallel() db := pgtest.NewSqlxDB(t) @@ -50,25 +46,14 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { require.NoError(t, err) defaultDuration := time.Second * 5 -<<<<<<< HEAD t.Run("updates attempt state and checks error returns", func(t *testing.T) { // Insert a transaction into persistent store inTx := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 1, fromAddress) -======= - t.Run("updates attempt state to broadcast and checks error returns", func(t *testing.T) { - // Insert a transaction into persistent store - inTx := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 1, fromAddress) - require.Nil(t, inTx.BroadcastAt) ->>>>>>> jtw/step-3-01 now := time.Now() // Insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) -<<<<<<< HEAD err := inMemoryStore.SaveInsufficientFundsAttempt( -======= - err := inMemoryStore.SaveSentAttempt( ->>>>>>> jtw/step-3-01 ctx, defaultDuration, &inTx.TxAttempts[0], @@ -83,7 +68,6 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { require.Equal(t, 1, len(actTxs)) actTx := actTxs[0] assertTxEqual(t, expTx, actTx) -<<<<<<< HEAD assert.Equal(t, txmgrtypes.TxAttemptInsufficientFunds, actTx.TxAttempts[0].State) // wrong tx id @@ -92,7 +76,65 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { expErr := persistentStore.SaveInsufficientFundsAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) assert.NoError(t, actErr) assert.NoError(t, expErr) -======= + inTx.TxAttempts[0].TxID = inTx.ID // reset + + // wrong attempt state + inTx.TxAttempts[0].State = txmgrtypes.TxAttemptBroadcast + actErr = inMemoryStore.SaveInsufficientFundsAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) + expErr = persistentStore.SaveInsufficientFundsAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) + assert.Error(t, actErr) + assert.Error(t, expErr) + inTx.TxAttempts[0].State = txmgrtypes.TxAttemptInsufficientFunds // reset + }) +} + +func TestInMemoryStore_SaveSentAttempt(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := testutils.Context(t) + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + + defaultDuration := time.Second * 5 + t.Run("updates attempt state to broadcast and checks error returns", func(t *testing.T) { + // Insert a transaction into persistent store + inTx := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 1, fromAddress) + require.Nil(t, inTx.BroadcastAt) + now := time.Now() + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + err := inMemoryStore.SaveSentAttempt( + ctx, + defaultDuration, + &inTx.TxAttempts[0], + now, + ) + require.NoError(t, err) + + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) + require.NoError(t, err) + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.Equal(t, 1, len(actTxs)) + actTx := actTxs[0] + assertTxEqual(t, expTx, actTx) assert.Equal(t, txmgrtypes.TxAttemptBroadcast, actTx.TxAttempts[0].State) // wrong tx id @@ -101,18 +143,10 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { expErr := persistentStore.SaveSentAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) assert.Error(t, actErr) assert.Error(t, expErr) ->>>>>>> jtw/step-3-01 inTx.TxAttempts[0].TxID = inTx.ID // reset // wrong attempt state inTx.TxAttempts[0].State = txmgrtypes.TxAttemptBroadcast -<<<<<<< HEAD - actErr = inMemoryStore.SaveInsufficientFundsAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) - expErr = persistentStore.SaveInsufficientFundsAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) - assert.Error(t, actErr) - assert.Error(t, expErr) - inTx.TxAttempts[0].State = txmgrtypes.TxAttemptInsufficientFunds // reset -======= actErr = inMemoryStore.SaveSentAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) expErr = persistentStore.SaveSentAttempt(ctx, defaultDuration, &inTx.TxAttempts[0], now) assert.Error(t, actErr) @@ -173,7 +207,6 @@ func TestInMemoryStore_Abandon(t *testing.T) { for i := 0; i < nTxs; i++ { assertTxEqual(t, *expTxs[i], actTxs[i]) } ->>>>>>> jtw/step-3-01 }) } From 900a1979520fb58c87dd44b87224d8e0317bae66 Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 1 Apr 2024 14:12:48 -0400 Subject: [PATCH 77/84] fix errors --- .../evm/txmgr/evm_inmemory_store_test.go | 132 +++++++++++------- 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index e46c6a32a50..256ba0d1b50 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -6,19 +6,13 @@ import ( "time" "github.com/ethereum/go-ethereum/common" -<<<<<<< HEAD -======= "github.com/google/uuid" ->>>>>>> jtw/step-3-01 "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/logger" commontxmgr "github.com/smartcontractkit/chainlink/v2/common/txmgr" -<<<<<<< HEAD -======= txmgrtypes "github.com/smartcontractkit/chainlink/v2/common/txmgr/types" ->>>>>>> jtw/step-3-01 evmgas "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtxmgr "github.com/smartcontractkit/chainlink/v2/core/chains/evm/txmgr" @@ -29,11 +23,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" ) -<<<<<<< HEAD func TestInMemoryStore_UpdateBroadcastAts(t *testing.T) { -======= -func TestInMemoryStore_UpdateTxCallbackCompleted(t *testing.T) { ->>>>>>> jtw/step-3-01 t.Parallel() db := pgtest.NewSqlxDB(t) @@ -56,9 +46,85 @@ func TestInMemoryStore_UpdateTxCallbackCompleted(t *testing.T) { ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) -<<<<<<< HEAD t.Run("does not update when broadcast_at is Null", func(t *testing.T) { -======= + // Insert a transaction into persistent store + inTx := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 1, fromAddress) + require.Nil(t, inTx.BroadcastAt) + now := time.Now() + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + err := inMemoryStore.UpdateBroadcastAts( + ctx, + now, + []int64{inTx.ID}, + ) + require.NoError(t, err) + + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) + require.NoError(t, err) + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.Equal(t, 1, len(actTxs)) + actTx := actTxs[0] + assertTxEqual(t, expTx, actTx) + assert.Nil(t, actTx.BroadcastAt) + }) + + t.Run("updates broadcast_at when not null", func(t *testing.T) { + // Insert a transaction into persistent store + time1 := time.Now() + inTx := cltest.NewEthTx(fromAddress) + inTx.Sequence = new(evmtypes.Nonce) + inTx.State = commontxmgr.TxUnconfirmed + inTx.BroadcastAt = &time1 + inTx.InitialBroadcastAt = &time1 + require.NoError(t, persistentStore.InsertTx(ctx, &inTx)) + // Insert the transaction into the in-memory store + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) + + time2 := time1.Add(1 * time.Hour) + err := inMemoryStore.UpdateBroadcastAts( + ctx, + time2, + []int64{inTx.ID}, + ) + require.NoError(t, err) + + expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) + require.NoError(t, err) + fn := func(tx *evmtxmgr.Tx) bool { return true } + actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) + require.Equal(t, 1, len(actTxs)) + actTx := actTxs[0] + assertTxEqual(t, expTx, actTx) + assert.NotNil(t, actTx.BroadcastAt) + }) +} + +func TestInMemoryStore_UpdateTxCallbackCompleted(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + _, dbcfg, evmcfg := evmtxmgr.MakeTestConfigs(t) + persistentStore := cltest.NewTestTxStore(t, db) + kst := cltest.NewKeyStore(t, db, dbcfg) + _, fromAddress := cltest.MustInsertRandomKey(t, kst.Eth()) + + ethClient := evmtest.NewEthClientMockWithDefaultChain(t) + lggr := logger.TestSugared(t) + chainID := ethClient.ConfiguredChainID() + ctx := testutils.Context(t) + + inMemoryStore, err := commontxmgr.NewInMemoryStore[ + *big.Int, + common.Address, common.Hash, common.Hash, + *evmtypes.Receipt, + evmtypes.Nonce, + evmgas.EvmFee, + ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + require.NoError(t, err) + t.Run("sets tx callback as completed", func(t *testing.T) { // Insert a transaction into persistent store inTx := cltest.NewEthTx(fromAddress) @@ -183,7 +249,6 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { defaultDuration := time.Second * 5 t.Run("updates attempt state to broadcast and checks error returns", func(t *testing.T) { ->>>>>>> jtw/step-3-01 // Insert a transaction into persistent store inTx := mustInsertInProgressEthTxWithAttempt(t, persistentStore, 1, fromAddress) require.Nil(t, inTx.BroadcastAt) @@ -191,18 +256,11 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { // Insert the transaction into the in-memory store require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) -<<<<<<< HEAD - err := inMemoryStore.UpdateBroadcastAts( - ctx, - now, - []int64{inTx.ID}, -======= err := inMemoryStore.SaveSentAttempt( ctx, defaultDuration, &inTx.TxAttempts[0], now, ->>>>>>> jtw/step-3-01 ) require.NoError(t, err) @@ -213,39 +271,6 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { require.Equal(t, 1, len(actTxs)) actTx := actTxs[0] assertTxEqual(t, expTx, actTx) -<<<<<<< HEAD - assert.Nil(t, actTx.BroadcastAt) - }) - - t.Run("updates broadcast_at when not null", func(t *testing.T) { - // Insert a transaction into persistent store - time1 := time.Now() - inTx := cltest.NewEthTx(fromAddress) - inTx.Sequence = new(evmtypes.Nonce) - inTx.State = commontxmgr.TxUnconfirmed - inTx.BroadcastAt = &time1 - inTx.InitialBroadcastAt = &time1 - require.NoError(t, persistentStore.InsertTx(ctx, &inTx)) - // Insert the transaction into the in-memory store - require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - - time2 := time1.Add(1 * time.Hour) - err := inMemoryStore.UpdateBroadcastAts( - ctx, - time2, - []int64{inTx.ID}, - ) - require.NoError(t, err) - - expTx, err := persistentStore.FindTxWithAttempts(ctx, inTx.ID) - require.NoError(t, err) - fn := func(tx *evmtxmgr.Tx) bool { return true } - actTxs := inMemoryStore.XXXTestFindTxs(nil, fn, inTx.ID) - require.Equal(t, 1, len(actTxs)) - actTx := actTxs[0] - assertTxEqual(t, expTx, actTx) - assert.NotNil(t, actTx.BroadcastAt) -======= assert.Equal(t, txmgrtypes.TxAttemptBroadcast, actTx.TxAttempts[0].State) // wrong tx id @@ -318,7 +343,6 @@ func TestInMemoryStore_Abandon(t *testing.T) { for i := 0; i < nTxs; i++ { assertTxEqual(t, *expTxs[i], actTxs[i]) } ->>>>>>> jtw/step-3-01 }) } From fdb59e6db39f65b3688c691571623a33ea531b4a Mon Sep 17 00:00:00 2001 From: James Walker Date: Mon, 1 Apr 2024 14:14:34 -0400 Subject: [PATCH 78/84] address comments --- common/txmgr/inmemory_store.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index a401f163d9f..934caa7185e 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -240,16 +240,16 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT } // UpdateBroadcastAts updates the broadcast_at time for a given set of attempts -func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateBroadcastAts(ctx context.Context, now time.Time, txIDs []int64) error { +func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) UpdateBroadcastAts(ctx context.Context, inTime time.Time, txIDs []int64) error { // Persist to persistent storage - if err := ms.persistentTxStore.UpdateBroadcastAts(ctx, now, txIDs); err != nil { + if err := ms.persistentTxStore.UpdateBroadcastAts(ctx, inTime, txIDs); err != nil { return err } // Update in memory store fn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) { - if tx.BroadcastAt != nil && tx.BroadcastAt.Before(now) { - tx.BroadcastAt = &now + if tx.BroadcastAt != nil && tx.BroadcastAt.Before(inTime) { + tx.BroadcastAt = &inTime } } From 85ff0e169e18f45d998b29c5bcb0430c0b587278 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 4 Apr 2024 14:37:43 -0400 Subject: [PATCH 79/84] remove wrong chain id test cases --- .../evm/txmgr/evm_inmemory_store_test.go | 84 +------------------ 1 file changed, 3 insertions(+), 81 deletions(-) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index b9a10c862eb..19b31501b01 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -255,9 +255,9 @@ func TestInMemoryStore_FindTxAttemptsRequiringResend(t *testing.T) { hasErr bool hasTxAttempts bool }{ - //{"finds nothing if transactions from a different key", time.Now(), 10, chainID, utils.RandomAddress(), false, false}, - //{"returns the highest price attempt for each transaction that was last broadcast before or on the given time", time.Unix(1616509200, 0), 0, chainID, fromAddress, false, true}, - //{"returns the highest price attempt for EIP-1559 transactions", time.Unix(1616509400, 0), 0, chainID, fromAddress, false, true}, + {"finds nothing if transactions from a different key", time.Now(), 10, chainID, evmutils.RandomAddress(), false, false}, + {"returns the highest price attempt for each transaction that was last broadcast before or on the given time", time.Unix(1616509200, 0), 0, chainID, fromAddress, false, true}, + {"returns the highest price attempt for EIP-1559 transactions", time.Unix(1616509400, 0), 0, chainID, fromAddress, false, true}, {"applies limit", time.Unix(1616509200, 0), 1, chainID, fromAddress, false, true}, } @@ -577,7 +577,6 @@ func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { {"no idempotency key", "", chainID, false, false}, {"wrong idempotency key", "wrong", chainID, false, false}, {"finds tx with idempotency key", idempotencyKey, chainID, false, true}, - {"wrong chain", idempotencyKey, big.NewInt(999), false, false}, } for _, tc := range tcs { @@ -646,7 +645,6 @@ func TestInMemoryStore_CheckTxQueueCapacity(t *testing.T) { {"capacity reached", fromAddress, 2, chainID, true}, {"above capacity", fromAddress, 1, chainID, true}, {"below capacity", fromAddress, 3, chainID, false}, - {"wrong chain", fromAddress, 2, big.NewInt(999), false}, {"wrong address", common.Address{}, 2, chainID, false}, {"max queued txs is 0", fromAddress, 0, chainID, false}, } @@ -710,7 +708,6 @@ func TestInMemoryStore_CountUnstartedTransactions(t *testing.T) { hasErr bool }{ {"return correct total transactions", fromAddress, chainID, 2, false}, - {"invalid chain id", fromAddress, big.NewInt(999), 0, false}, {"invalid address", common.Address{}, chainID, 0, false}, } @@ -772,7 +769,6 @@ func TestInMemoryStore_CountUnconfirmedTransactions(t *testing.T) { hasErr bool }{ {"return correct total transactions", fromAddress, chainID, 3, false}, - {"invalid chain id", fromAddress, big.NewInt(999), 0, false}, {"invalid address", common.Address{}, chainID, 0, false}, } @@ -844,7 +840,6 @@ func TestInMemoryStore_FindTxAttemptsConfirmedMissingReceipt(t *testing.T) { hasError bool }{ {"finds tx attempts confirmed missing receipt", chainID, 3, false}, - {"wrong chain", big.NewInt(999), 0, false}, } for _, tc := range tcs { @@ -918,7 +913,6 @@ func TestInMemoryStore_FindTxAttemptsRequiringReceiptFetch(t *testing.T) { hasError bool }{ {"finds tx attempts requiring receipt fetch", chainID, 3, false}, - {"wrong chain", big.NewInt(999), 0, false}, } for _, tc := range tcs { @@ -1175,14 +1169,6 @@ func TestInMemoryStore_CountTransactionsByState(t *testing.T) { require.NoError(t, actErr) assert.Equal(t, expCount, actCount) }) - t.Run("wrong chain id", func(t *testing.T) { - wrongChainID := big.NewInt(999) - expCount, expErr := persistentStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, wrongChainID) - actCount, actErr := inMemoryStore.CountTransactionsByState(ctx, commontxmgr.TxUnconfirmed, wrongChainID) - require.NoError(t, expErr) - require.NoError(t, actErr) - assert.Equal(t, expCount, actCount) - }) t.Run("3 unconfirmed transactions", func(t *testing.T) { for i := int64(0); i < 3; i++ { // insert the transaction into the persistent store @@ -1268,15 +1254,6 @@ func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testi } }) - t.Run("does not return txes with different chainID", func(t *testing.T) { - wrongChainID := big.NewInt(999) - expTxs, expErr := persistentStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, wrongChainID) - actTxs, actErr := inMemoryStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, fromAddress, wrongChainID) - require.NoError(t, expErr) - require.NoError(t, actErr) - assert.Equal(t, len(expTxs), len(actTxs)) - }) - t.Run("does not return txes with different fromAddress", func(t *testing.T) { anotherFromAddress := common.Address{} expTxs, expErr := persistentStore.FindTxsRequiringResubmissionDueToInsufficientFunds(ctx, anotherFromAddress, chainID) @@ -1336,15 +1313,6 @@ func TestInMemoryStore_GetNonFatalTransactions(t *testing.T) { assertTxEqual(t, *expTxs[i], *actTxs[i]) } }) - - t.Run("wrong chain ID", func(t *testing.T) { - wrongChainID := big.NewInt(999) - expTxs, expErr := persistentStore.GetNonFatalTransactions(ctx, wrongChainID) - actTxs, actErr := inMemoryStore.GetNonFatalTransactions(ctx, wrongChainID) - require.NoError(t, expErr) - require.NoError(t, actErr) - assert.Equal(t, len(expTxs), len(actTxs)) - }) } func TestInMemoryStore_FindTransactionsConfirmedInBlockRange(t *testing.T) { @@ -1399,15 +1367,6 @@ func TestInMemoryStore_FindTransactionsConfirmedInBlockRange(t *testing.T) { assertTxEqual(t, *expTxs[i], *actTxs[i]) } }) - - t.Run("wrong chain ID", func(t *testing.T) { - wrongChainID := big.NewInt(999) - expTxs, expErr := persistentStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, wrongChainID) - actTxs, actErr := inMemoryStore.FindTransactionsConfirmedInBlockRange(ctx, 10, 8, wrongChainID) - require.NoError(t, expErr) - require.NoError(t, actErr) - assert.Equal(t, len(expTxs), len(actTxs)) - }) } func TestInMemoryStore_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { @@ -1455,16 +1414,6 @@ func TestInMemoryStore_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { require.True(t, actBroadcastAt.Valid) assert.Equal(t, expBroadcastAt.Time.Unix(), actBroadcastAt.Time.Unix()) }) - t.Run("wrong chain ID", func(t *testing.T) { - wrongChainID := big.NewInt(999) - expBroadcastAt, expErr := persistentStore.FindEarliestUnconfirmedBroadcastTime(ctx, wrongChainID) - actBroadcastAt, actErr := inMemoryStore.FindEarliestUnconfirmedBroadcastTime(ctx, wrongChainID) - require.NoError(t, expErr) - require.NoError(t, actErr) - assert.False(t, expBroadcastAt.Valid) - assert.False(t, actBroadcastAt.Valid) - }) - } func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { @@ -1519,16 +1468,6 @@ func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { assert.True(t, actBlock.Valid) assert.Equal(t, expBlock.Int64, actBlock.Int64) }) - - t.Run("wrong chain ID", func(t *testing.T) { - wrongChainID := big.NewInt(999) - expBlock, expErr := persistentStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, wrongChainID) - actBlock, actErr := inMemoryStore.FindEarliestUnconfirmedTxAttemptBlock(ctx, wrongChainID) - require.NoError(t, expErr) - require.NoError(t, actErr) - assert.False(t, expBlock.Valid) - assert.False(t, actBlock.Valid) - }) } func TestInMemoryStore_LoadTxAttempts(t *testing.T) { @@ -1675,23 +1614,6 @@ func TestInMemoryStore_IsTxFinalized(t *testing.T) { require.NoError(t, actErr) assert.Equal(t, expIsFinalized, actIsFinalized) }) - - t.Run("wrong chain ID", func(t *testing.T) { - // insert the transaction into the persistent store - inTx := cltest.MustInsertConfirmedEthTxWithLegacyAttempt(t, persistentStore, 133, 3, fromAddress) - rec := mustInsertEthReceipt(t, persistentStore, 3, evmutils.NewHash(), inTx.TxAttempts[0].Hash) - // insert the transaction into the in-memory store - inTx.TxAttempts[0].Receipts = append(inTx.TxAttempts[0].Receipts, evmtxmgr.DbReceiptToEvmReceipt(&rec)) - require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - - blockHeight := int64(10) - wrongChainID := big.NewInt(999) - expIsFinalized, expErr := persistentStore.IsTxFinalized(ctx, blockHeight, inTx.ID, wrongChainID) - actIsFinalized, actErr := inMemoryStore.IsTxFinalized(ctx, blockHeight, inTx.ID, wrongChainID) - require.NoError(t, expErr) - require.NoError(t, actErr) - assert.Equal(t, expIsFinalized, actIsFinalized) - }) } func TestInMemoryStore_FindTxsRequiringGasBump(t *testing.T) { From 759b962a85d0a15f3d99812ed4cf22e2ba50e858 Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 4 Apr 2024 14:39:12 -0400 Subject: [PATCH 80/84] remove setting Tx in txAttempts --- common/txmgr/address_state.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index a7674883667..e64857aecb3 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -520,7 +520,6 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) _findT for i := 0; i < len(tx.TxAttempts); i++ { txAttempt := tx.TxAttempts[i] if txAttemptFilter(&txAttempt) { - txAttempt.Tx = *tx txAttempts = append(txAttempts, txAttempt) } } @@ -535,7 +534,6 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) _findT for i := 0; i < len(tx.TxAttempts); i++ { txAttempt := tx.TxAttempts[i] if txAttemptFilter(&txAttempt) { - txAttempt.Tx = *tx txAttempts = append(txAttempts, txAttempt) } } From 431fdf3aca64b8de30c86b979a4c3a43e30b601e Mon Sep 17 00:00:00 2001 From: James Walker Date: Thu, 4 Apr 2024 14:44:18 -0400 Subject: [PATCH 81/84] bypass all functions that cant replicate sorting --- common/txmgr/inmemory_store.go | 220 ++++++++++++++++++--------------- 1 file changed, 119 insertions(+), 101 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 1489811e258..76958a6d460 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -332,41 +332,47 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error, ) { - if ms.chainID.String() != chainID.String() { - panic("invalid chain ID") - } + return ms.persistentTxStore.FindTxAttemptsConfirmedMissingReceipt(ctx, chainID) - txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - return tx.TxAttempts != nil && len(tx.TxAttempts) > 0 - } - txAttemptFilter := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - return true - } - states := []txmgrtypes.TxState{TxConfirmedMissingReceipt} - attempts := []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} - ms.addressStatesLock.RLock() - defer ms.addressStatesLock.RUnlock() - for _, as := range ms.addressStates { - attempts = append(attempts, as.findTxAttempts(states, txFilter, txAttemptFilter)...) - } - // sort by tx_id ASC, gas_price DESC, gas_tip_cap DESC - // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee - slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { - aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence - if aSequence == nil || bSequence == nil { - return 0 + // TODO: THE SORTING OF GAS PRICE AND GAS TIP CAP IS NOT IMPLEMENTED CORRECTLY. NEED TO FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee + // THIS WORK WILL NEED TO BE DONE IN A SEPARATE PR WHICH CHANGES THE FEE GENERIC + /* + if ms.chainID.String() != chainID.String() { + panic("invalid chain ID") } - return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) - }) + txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return tx.TxAttempts != nil && len(tx.TxAttempts) > 0 + } + txAttemptFilter := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return true + } + states := []txmgrtypes.TxState{TxConfirmedMissingReceipt} + attempts := []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + attempts = append(attempts, as.findTxAttempts(states, txFilter, txAttemptFilter)...) + } + // sort by tx_id ASC, gas_price DESC, gas_tip_cap DESC + // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee + slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { + aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence + if aSequence == nil || bSequence == nil { + return 0 + } - // deep copy the attempts - var eAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] - for _, attempt := range attempts { - eAttempts = append(eAttempts, ms.deepCopyTxAttempt(attempt.Tx, attempt)) - } + return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) + }) - return eAttempts, nil + // deep copy the attempts + var eAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + for _, attempt := range attempts { + eAttempts = append(eAttempts, ms.deepCopyTxAttempt(attempt.Tx, attempt)) + } + + return eAttempts, nil + */ } // UpdateBroadcastAts updates the broadcast_at time for a given set of attempts @@ -402,41 +408,47 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT attempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], err error, ) { - if ms.chainID.String() != chainID.String() { - panic("invalid chain ID") - } + return ms.persistentTxStore.FindTxAttemptsRequiringReceiptFetch(ctx, chainID) + + // TODO: THE SORTING OF GAS PRICE AND GAS TIP CAP IS NOT IMPLEMENTED CORRECTLY. NEED TO FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee + // THIS WORK WILL NEED TO BE DONE IN A SEPARATE PR WHICH CHANGES THE FEE GENERIC + /* + if ms.chainID.String() != chainID.String() { + panic("invalid chain ID") + } + + txFilterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return len(tx.TxAttempts) > 0 + } + txAttemptFilterFn := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return attempt.State != txmgrtypes.TxAttemptInsufficientFunds + } + states := []txmgrtypes.TxState{TxUnconfirmed, TxConfirmedMissingReceipt} + attempts = []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + for _, as := range ms.addressStates { + attempts = append(attempts, as.findTxAttempts(states, txFilterFn, txAttemptFilterFn)...) + } + // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC + // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee + slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { + aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence + if aSequence == nil || bSequence == nil { + return 0 + } - txFilterFn := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - return len(tx.TxAttempts) > 0 - } - txAttemptFilterFn := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - return attempt.State != txmgrtypes.TxAttemptInsufficientFunds - } - states := []txmgrtypes.TxState{TxUnconfirmed, TxConfirmedMissingReceipt} - attempts = []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} - ms.addressStatesLock.RLock() - defer ms.addressStatesLock.RUnlock() - for _, as := range ms.addressStates { - attempts = append(attempts, as.findTxAttempts(states, txFilterFn, txAttemptFilterFn)...) - } - // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC - // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee - slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { - aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence - if aSequence == nil || bSequence == nil { - return 0 - } + return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) + }) - return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) - }) - - // deep copy the attempts - var eAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] - for _, attempt := range attempts { - eAttempts = append(eAttempts, ms.deepCopyTxAttempt(attempt.Tx, attempt)) - } + // deep copy the attempts + var eAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + for _, attempt := range attempts { + eAttempts = append(eAttempts, ms.deepCopyTxAttempt(attempt.Tx, attempt)) + } - return eAttempts, nil + return eAttempts, nil + */ } func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxesPendingCallback(ctx context.Context, blockNum int64, chainID CHAIN_ID) ( @@ -751,51 +763,57 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT return etxs, nil } -func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxAttemptsRequiringResend(_ context.Context, olderThan time.Time, maxInFlightTransactions uint32, chainID CHAIN_ID, address ADDR) ([]txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { - if ms.chainID.String() != chainID.String() { - panic("invalid chain ID") - } - - ms.addressStatesLock.RLock() - defer ms.addressStatesLock.RUnlock() - as, ok := ms.addressStates[address] - if !ok { - return nil, nil - } +func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxAttemptsRequiringResend(ctx context.Context, olderThan time.Time, maxInFlightTransactions uint32, chainID CHAIN_ID, address ADDR) ([]txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + return ms.persistentTxStore.FindTxAttemptsRequiringResend(ctx, olderThan, maxInFlightTransactions, chainID, address) - txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - if len(tx.TxAttempts) == 0 { - return false + // TODO: THE SORTING OF GAS PRICE AND GAS TIP CAP IS NOT IMPLEMENTED CORRECTLY. NEED TO FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee + // THIS WORK WILL NEED TO BE DONE IN A SEPARATE PR WHICH CHANGES THE FEE GENERIC + /* + if ms.chainID.String() != chainID.String() { + panic("invalid chain ID") } - return tx.BroadcastAt.Before(olderThan) || tx.BroadcastAt.Equal(olderThan) - } - txAttemptFilter := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - return attempt.State != txmgrtypes.TxAttemptInProgress - } - states := []txmgrtypes.TxState{TxUnconfirmed, TxConfirmedMissingReceipt} - attempts := as.findTxAttempts(states, txFilter, txAttemptFilter) - // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC - // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee - slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { - aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence - if aSequence == nil || bSequence == nil { - return 0 + + ms.addressStatesLock.RLock() + defer ms.addressStatesLock.RUnlock() + as, ok := ms.addressStates[address] + if !ok { + return nil, nil } - return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) - }) - // LIMIT by maxInFlightTransactions - if maxInFlightTransactions > 0 && len(attempts) > int(maxInFlightTransactions) { - attempts = attempts[:maxInFlightTransactions] - } + txFilter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + if len(tx.TxAttempts) == 0 { + return false + } + return tx.BroadcastAt.Before(olderThan) || tx.BroadcastAt.Equal(olderThan) + } + txAttemptFilter := func(attempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { + return attempt.State != txmgrtypes.TxAttemptInProgress + } + states := []txmgrtypes.TxState{TxUnconfirmed, TxConfirmedMissingReceipt} + attempts := as.findTxAttempts(states, txFilter, txAttemptFilter) + // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC + // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee + slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { + aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence + if aSequence == nil || bSequence == nil { + return 0 + } - // deep copy the attempts - var eAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] - for _, attempt := range attempts { - eAttempts = append(eAttempts, ms.deepCopyTxAttempt(attempt.Tx, attempt)) - } + return cmp.Compare((*aSequence).Int64(), (*bSequence).Int64()) + }) + // LIMIT by maxInFlightTransactions + if maxInFlightTransactions > 0 && len(attempts) > int(maxInFlightTransactions) { + attempts = attempts[:maxInFlightTransactions] + } - return eAttempts, nil + // deep copy the attempts + var eAttempts []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + for _, attempt := range attempts { + eAttempts = append(eAttempts, ms.deepCopyTxAttempt(attempt.Tx, attempt)) + } + + return eAttempts, nil + */ } func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxWithSequence(_ context.Context, fromAddress ADDR, seq SEQ) (*txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { From 41949b90c290a5dd0cbbd3d771ca843805a126fb Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 19 Apr 2024 12:36:19 -0400 Subject: [PATCH 82/84] update TODO comments --- common/txmgr/inmemory_store.go | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 76958a6d460..696d0fe64aa 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -197,7 +197,6 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) Count // CountUnstartedTransactions returns the number of unstarted transactions for a given address. // Unstarted transactions are transactions that have not been broadcast yet. -// NOTE(jtw): used to calculate total inflight transactions func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountUnstartedTransactions(ctx context.Context, fromAddress ADDR, chainID CHAIN_ID) (uint32, error) { if ms.chainID.String() != chainID.String() { panic("invalid chain ID") @@ -334,8 +333,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT ) { return ms.persistentTxStore.FindTxAttemptsConfirmedMissingReceipt(ctx, chainID) - // TODO: THE SORTING OF GAS PRICE AND GAS TIP CAP IS NOT IMPLEMENTED CORRECTLY. NEED TO FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee - // THIS WORK WILL NEED TO BE DONE IN A SEPARATE PR WHICH CHANGES THE FEE GENERIC + // TODO(BCI-3115) /* if ms.chainID.String() != chainID.String() { panic("invalid chain ID") @@ -355,7 +353,6 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT attempts = append(attempts, as.findTxAttempts(states, txFilter, txAttemptFilter)...) } // sort by tx_id ASC, gas_price DESC, gas_tip_cap DESC - // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence if aSequence == nil || bSequence == nil { @@ -410,8 +407,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT ) { return ms.persistentTxStore.FindTxAttemptsRequiringReceiptFetch(ctx, chainID) - // TODO: THE SORTING OF GAS PRICE AND GAS TIP CAP IS NOT IMPLEMENTED CORRECTLY. NEED TO FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee - // THIS WORK WILL NEED TO BE DONE IN A SEPARATE PR WHICH CHANGES THE FEE GENERIC + // TODO(BCI-3115) /* if ms.chainID.String() != chainID.String() { panic("invalid chain ID") @@ -431,7 +427,6 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT attempts = append(attempts, as.findTxAttempts(states, txFilterFn, txAttemptFilterFn)...) } // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC - // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence if aSequence == nil || bSequence == nil { @@ -766,8 +761,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxAttemptsRequiringResend(ctx context.Context, olderThan time.Time, maxInFlightTransactions uint32, chainID CHAIN_ID, address ADDR) ([]txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { return ms.persistentTxStore.FindTxAttemptsRequiringResend(ctx, olderThan, maxInFlightTransactions, chainID, address) - // TODO: THE SORTING OF GAS PRICE AND GAS TIP CAP IS NOT IMPLEMENTED CORRECTLY. NEED TO FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee - // THIS WORK WILL NEED TO BE DONE IN A SEPARATE PR WHICH CHANGES THE FEE GENERIC + // TODO(BCI-3115) /* if ms.chainID.String() != chainID.String() { panic("invalid chain ID") @@ -792,7 +786,7 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT states := []txmgrtypes.TxState{TxUnconfirmed, TxConfirmedMissingReceipt} attempts := as.findTxAttempts(states, txFilter, txAttemptFilter) // sort by sequence ASC, gas_price DESC, gas_tip_cap DESC - // TODO: FIGURE OUT HOW TO GET GAS PRICE AND GAS TIP CAP FROM TxFee + // TODO(BCI-3115) slices.SortFunc(attempts, func(a, b txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) int { aSequence, bSequence := a.Tx.Sequence, b.Tx.Sequence if aSequence == nil || bSequence == nil { From e17ae3111636c78c8ed17a8eeffcc0eeb9d14eda Mon Sep 17 00:00:00 2001 From: James Walker Date: Fri, 19 Apr 2024 13:12:48 -0400 Subject: [PATCH 83/84] apply refactor --- common/txmgr/address_state.go | 9 +++++++++ common/txmgr/inmemory_store.go | 10 ++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/common/txmgr/address_state.go b/common/txmgr/address_state.go index e64857aecb3..2c82fa50ce7 100644 --- a/common/txmgr/address_state.go +++ b/common/txmgr/address_state.go @@ -302,6 +302,15 @@ func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) findTx return txAttempts } +// findTxByID returns the transaction with the given ID. +// If no transaction is found, nil is returned. +func (as *addressState[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) findTxByID(txID int64) *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] { + as.RLock() + defer as.RUnlock() + + return as.allTxs[txID] +} + // findTxs returns all transactions that match the given filters. // If txIDs are provided, only the transactions with those IDs are considered. // If no txIDs are provided, all transactions are considered. diff --git a/common/txmgr/inmemory_store.go b/common/txmgr/inmemory_store.go index 696d0fe64aa..2148125d97d 100644 --- a/common/txmgr/inmemory_store.go +++ b/common/txmgr/inmemory_store.go @@ -460,13 +460,10 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindT } for i := 0; i < len(tx.TxAttempts); i++ { - if len(tx.TxAttempts[i].Receipts) == 0 { + if len(tx.TxAttempts[i].Receipts) == 0 || !tx.PipelineTaskRunID.Valid || !tx.SignalCallback || tx.CallbackCompleted { continue } - if !tx.PipelineTaskRunID.Valid || !tx.SignalCallback || tx.CallbackCompleted { - continue - } receipt := tx.TxAttempts[i].Receipts[0] minConfirmations := int64(tx.MinConfirmations.Uint32) if receipt.GetBlockNumber() != nil && @@ -1053,11 +1050,8 @@ func (ms *inMemoryStore[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) LoadT return nil } - filter := func(tx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) bool { - return tx.ID == etx.ID - } txAttempts := []txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]{} - for _, tx := range as.findTxs(nil, filter, etx.ID) { + if tx := as.findTxByID(etx.ID); tx != nil { for _, txAttempt := range tx.TxAttempts { txAttempts = append(txAttempts, ms.deepCopyTxAttempt(*etx, txAttempt)) } From b2e7606b1a92dbe65fc7b5bffd898cb0c90ca3d8 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Thu, 27 Jun 2024 11:55:06 -0500 Subject: [PATCH 84/84] Generated mocks and fixed lint errors --- common/txmgr/types/mocks/tx_store.go | 30 ++ .../evm/txmgr/evm_inmemory_store_test.go | 293 +++--------------- core/chains/evm/txmgr/mocks/evm_tx_store.go | 30 ++ 3 files changed, 108 insertions(+), 245 deletions(-) diff --git a/common/txmgr/types/mocks/tx_store.go b/common/txmgr/types/mocks/tx_store.go index 814207d3986..0c141d44f2f 100644 --- a/common/txmgr/types/mocks/tx_store.go +++ b/common/txmgr/types/mocks/tx_store.go @@ -700,6 +700,36 @@ func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) FindTxsRequ return r0, r1 } +// GetAllTransactions provides a mock function with given fields: ctx, chainID +func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetAllTransactions(ctx context.Context, chainID CHAIN_ID) ([]txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { + ret := _m.Called(ctx, chainID) + + if len(ret) == 0 { + panic("no return value specified for GetAllTransactions") + } + + var r0 []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, CHAIN_ID) ([]txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error)); ok { + return rf(ctx, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, CHAIN_ID) []txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]); ok { + r0 = rf(ctx, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, CHAIN_ID) error); ok { + r1 = rf(ctx, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetInProgressTxAttempts provides a mock function with given fields: ctx, address, chainID func (_m *TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) GetInProgressTxAttempts(ctx context.Context, address ADDR, chainID CHAIN_ID) ([]txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], error) { ret := _m.Called(ctx, address, chainID) diff --git a/core/chains/evm/txmgr/evm_inmemory_store_test.go b/core/chains/evm/txmgr/evm_inmemory_store_test.go index 19b31501b01..9b9acbc15dd 100644 --- a/core/chains/evm/txmgr/evm_inmemory_store_test.go +++ b/core/chains/evm/txmgr/evm_inmemory_store_test.go @@ -44,13 +44,7 @@ func TestInMemoryStore_FindTxesPendingCallback(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) head := evmtypes.Head{ @@ -201,13 +195,7 @@ func TestInMemoryStore_FindTxAttemptsRequiringResend(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) // insert the transaction into the persistent store @@ -302,13 +290,7 @@ func TestInMemoryStore_FindTxesWithMetaFieldByReceiptBlockNum(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) // initialize the Meta field which is sqlutil.JSON @@ -397,13 +379,7 @@ func TestInMemoryStore_FindTxesWithMetaFieldByStates(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) // initialize the Meta field which is sqlutil.JSON @@ -472,13 +448,7 @@ func TestInMemoryStore_FindTxesByMetaFieldAndStates(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) // initialize the Meta field which is sqlutil.JSON @@ -549,13 +519,7 @@ func TestInMemoryStore_FindTxWithIdempotencyKey(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) idempotencyKey := "777" @@ -614,25 +578,17 @@ func TestInMemoryStore_CheckTxQueueCapacity(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) - inTxs := []evmtxmgr.Tx{ - cltest.NewEthTx(fromAddress), - cltest.NewEthTx(fromAddress), - } - for _, inTx := range inTxs { - // insert the transaction into the persistent store - require.NoError(t, persistentStore.InsertTx(ctx, &inTx)) - // insert the transaction into the in-memory store - require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - } + // insert the transaction into the persistent store + // insert the transaction into the in-memory store + tx1 := cltest.NewEthTx(fromAddress) + require.NoError(t, persistentStore.InsertTx(ctx, &tx1)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &tx1)) + tx2 := cltest.NewEthTx(fromAddress) + require.NoError(t, persistentStore.InsertTx(ctx, &tx2)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &tx2)) tcs := []struct { name string @@ -678,26 +634,17 @@ func TestInMemoryStore_CountUnstartedTransactions(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) - // initialize unstarted transactions - inUnstartedTxs := []evmtxmgr.Tx{ - cltest.NewEthTx(fromAddress), - cltest.NewEthTx(fromAddress), - } - for _, inTx := range inUnstartedTxs { - // insert the transaction into the persistent store - require.NoError(t, persistentStore.InsertTx(ctx, &inTx)) - // insert the transaction into the in-memory store - require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &inTx)) - } + // insert the transaction into the persistent store + // insert the transaction into the in-memory store + tx1 := cltest.NewEthTx(fromAddress) + require.NoError(t, persistentStore.InsertTx(ctx, &tx1)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &tx1)) + tx2 := cltest.NewEthTx(fromAddress) + require.NoError(t, persistentStore.InsertTx(ctx, &tx2)) + require.NoError(t, inMemoryStore.XXXTestInsertTx(fromAddress, &tx2)) tcs := []struct { name string @@ -742,13 +689,7 @@ func TestInMemoryStore_CountUnconfirmedTransactions(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) // initialize unconfirmed transactions @@ -803,13 +744,7 @@ func TestInMemoryStore_FindTxAttemptsConfirmedMissingReceipt(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) // initialize transactions @@ -876,13 +811,7 @@ func TestInMemoryStore_FindTxAttemptsRequiringReceiptFetch(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) // initialize transactions @@ -949,13 +878,7 @@ func TestInMemoryStore_GetInProgressTxAttempts(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("gets 0 in progress transaction", func(t *testing.T) { @@ -997,13 +920,7 @@ func TestInMemoryStore_HasInProgressTransaction(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("no in progress transaction", func(t *testing.T) { @@ -1042,13 +959,7 @@ func TestInMemoryStore_GetTxByID(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("no transaction", func(t *testing.T) { @@ -1090,13 +1001,7 @@ func TestInMemoryStore_FindTxWithSequence(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("no results", func(t *testing.T) { @@ -1153,13 +1058,7 @@ func TestInMemoryStore_CountTransactionsByState(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("no results", func(t *testing.T) { @@ -1200,13 +1099,7 @@ func TestInMemoryStore_FindTxsRequiringResubmissionDueToInsufficientEth(t *testi chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("no results", func(t *testing.T) { @@ -1278,13 +1171,7 @@ func TestInMemoryStore_GetNonFatalTransactions(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("no results", func(t *testing.T) { @@ -1329,13 +1216,7 @@ func TestInMemoryStore_FindTransactionsConfirmedInBlockRange(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("no results", func(t *testing.T) { @@ -1383,13 +1264,7 @@ func TestInMemoryStore_FindEarliestUnconfirmedBroadcastTime(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("no results", func(t *testing.T) { @@ -1430,13 +1305,7 @@ func TestInMemoryStore_FindEarliestUnconfirmedTxAttemptBlock(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("no results", func(t *testing.T) { @@ -1484,13 +1353,7 @@ func TestInMemoryStore_LoadTxAttempts(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("load tx attempt", func(t *testing.T) { @@ -1529,13 +1392,7 @@ func TestInMemoryStore_PreloadTxes(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("load transaction", func(t *testing.T) { @@ -1574,13 +1431,7 @@ func TestInMemoryStore_IsTxFinalized(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("tx not past finality depth", func(t *testing.T) { @@ -1630,13 +1481,7 @@ func TestInMemoryStore_FindTxsRequiringGasBump(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("gets transactions requiring gas bumping", func(t *testing.T) { @@ -1681,13 +1526,7 @@ func TestInMemoryStore_SaveInProgressAttempt(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("saves new in_progress attempt if attempt is new", func(t *testing.T) { @@ -1788,13 +1627,7 @@ func TestInMemoryStore_UpdateBroadcastAts(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("does not update when broadcast_at is Null", func(t *testing.T) { @@ -1867,13 +1700,7 @@ func TestInMemoryStore_SetBroadcastBeforeBlockNum(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("saves block num to unconfirmed evm.tx_attempts without one", func(t *testing.T) { @@ -1938,13 +1765,7 @@ func TestInMemoryStore_UpdateTxCallbackCompleted(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("sets tx callback as completed", func(t *testing.T) { @@ -1994,13 +1815,7 @@ func TestInMemoryStore_SaveInsufficientFundsAttempt(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) defaultDuration := time.Second * 5 @@ -2060,13 +1875,7 @@ func TestInMemoryStore_SaveSentAttempt(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) defaultDuration := time.Second * 5 @@ -2127,13 +1936,7 @@ func TestInMemoryStore_Abandon(t *testing.T) { chainID := ethClient.ConfiguredChainID() ctx := testutils.Context(t) - inMemoryStore, err := commontxmgr.NewInMemoryStore[ - *big.Int, - common.Address, common.Hash, common.Hash, - *evmtypes.Receipt, - evmtypes.Nonce, - evmgas.EvmFee, - ](ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) + inMemoryStore, err := commontxmgr.NewInMemoryStore(ctx, lggr, chainID, kst.Eth(), persistentStore, evmcfg.Transactions()) require.NoError(t, err) t.Run("Abandon transactions successfully", func(t *testing.T) { diff --git a/core/chains/evm/txmgr/mocks/evm_tx_store.go b/core/chains/evm/txmgr/mocks/evm_tx_store.go index 61c948c1ff4..e1505a045dc 100644 --- a/core/chains/evm/txmgr/mocks/evm_tx_store.go +++ b/core/chains/evm/txmgr/mocks/evm_tx_store.go @@ -821,6 +821,36 @@ func (_m *EvmTxStore) FindTxsRequiringResubmissionDueToInsufficientFunds(ctx con return r0, r1 } +// GetAllTransactions provides a mock function with given fields: ctx, chainID +func (_m *EvmTxStore) GetAllTransactions(ctx context.Context, chainID *big.Int) ([]types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { + ret := _m.Called(ctx, chainID) + + if len(ret) == 0 { + panic("no return value specified for GetAllTransactions") + } + + var r0 []types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) ([]types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error)); ok { + return rf(ctx, chainID) + } + if rf, ok := ret.Get(0).(func(context.Context, *big.Int) []types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]); ok { + r0 = rf(ctx, chainID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Tx[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *big.Int) error); ok { + r1 = rf(ctx, chainID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetInProgressTxAttempts provides a mock function with given fields: ctx, address, chainID func (_m *EvmTxStore) GetInProgressTxAttempts(ctx context.Context, address common.Address, chainID *big.Int) ([]types.TxAttempt[*big.Int, common.Address, common.Hash, common.Hash, evmtypes.Nonce, gas.EvmFee], error) { ret := _m.Called(ctx, address, chainID)