diff --git a/.mockery.yml b/.mockery.yml index d08c7c6..fbf9221 100644 --- a/.mockery.yml +++ b/.mockery.yml @@ -4,14 +4,7 @@ filename: "{{.InterfaceName | lower }}.generated.go" mockname: "{{.InterfaceName}}" outpkg: "mocks" packages: - github.com/0xPolygon/zkevm-ethtx-manager/ethtxmanager: + github.com/0xPolygon/zkevm-ethtx-manager/types: interfaces: EthermanInterface: - config: - StorageInterface: - config: - dir: "{{.InterfaceDir}}" - filename: "{{.InterfaceName | lower }}.generated.go" - mockname: "{{.InterfaceName}}Mock" - outpkg: "{{.PackageName}}" - inpackage: True + config: \ No newline at end of file diff --git a/ethtxmanager/ethtxmanager.go b/ethtxmanager/ethtxmanager.go index b9a6bd3..4431a34 100644 --- a/ethtxmanager/ethtxmanager.go +++ b/ethtxmanager/ethtxmanager.go @@ -48,8 +48,8 @@ type Client struct { cancel context.CancelFunc cfg Config - etherman EthermanInterface - storage StorageInterface + etherman types.EthermanInterface + storage types.StorageInterface from common.Address } @@ -173,13 +173,13 @@ func pendingL1Txs(URL string, from common.Address, httpHeaders map[string]string // Add a transaction to be sent and monitored func (c *Client) Add(ctx context.Context, to *common.Address, value *big.Int, - data []byte, gasOffset uint64, sidecar *types.BlobTxSidecar) (common.Hash, error) { + data []byte, gasOffset uint64, sidecar *ethTypes.BlobTxSidecar) (common.Hash, error) { return c.add(ctx, to, value, data, gasOffset, sidecar, 0) } // AddWithGas adds a transaction to be sent and monitored with a defined gas to be used so it's not estimated func (c *Client) AddWithGas(ctx context.Context, to *common.Address, - value *big.Int, data []byte, gasOffset uint64, sidecar *types.BlobTxSidecar, gas uint64) (common.Hash, error) { + value *big.Int, data []byte, gasOffset uint64, sidecar *ethTypes.BlobTxSidecar, gas uint64) (common.Hash, error) { return c.add(ctx, to, value, data, gasOffset, sidecar, gas) } @@ -486,7 +486,7 @@ func (c *Client) monitorTxs(ctx context.Context) error { for _, mTx := range iterations { mTx := mTx // force variable shadowing to avoid pointer conflicts go func(c *Client, mTx *monitoredTxnIteration) { - mTxLogger := createMonitoredTxLogger(*mTx.monitoredTx) + mTxLogger := createMonitoredTxLogger(*mTx.MonitoredTx) defer func(mTxLogger *log.Logger) { if err := recover(); err != nil { mTxLogger.Errorf("monitoring recovered from this err: %v", err) @@ -591,10 +591,10 @@ func (c *Client) monitorTx(ctx context.Context, mTx *monitoredTxnIteration, logg var err error logger.Info("processing") - var signedTx *types.Transaction + var signedTx *ethTypes.Transaction if !mTx.confirmed { // review tx and increase gas and gas price if needed - if mTx.Status == MonitoredTxStatusSent { + if mTx.Status == types.MonitoredTxStatusSent { err := c.reviewMonitoredTxGas(ctx, mTx, logger) if err != nil { logger.Errorf("failed to review monitored tx: %v", err) @@ -623,7 +623,7 @@ func (c *Client) monitorTx(ctx context.Context, mTx *monitoredTxnIteration, logg return } else { // update monitored tx changes into storage - err = c.storage.Update(ctx, *mTx.monitoredTx) + err = c.storage.Update(ctx, *mTx.MonitoredTx) if err != nil { logger.Errorf("failed to update monitored tx: %v", err) return @@ -647,7 +647,7 @@ func (c *Client) monitorTx(ctx context.Context, mTx *monitoredTxnIteration, logg mTx.Status = types.MonitoredTxStatusSent logger.Debugf("status changed to %v", string(mTx.Status)) // update monitored tx changes into storage - err = c.storage.Update(ctx, *mTx.monitoredTx) + err = c.storage.Update(ctx, *mTx.MonitoredTx) if err != nil { logger.Errorf("failed to update monitored tx changes: %v", err) return @@ -697,8 +697,8 @@ func (c *Client) monitorTx(ctx context.Context, mTx *monitoredTxnIteration, logg } // if mined, check receipt and mark as Failed or Confirmed - if mTx.lastReceipt.Status == types.ReceiptStatusSuccessful { - mTx.Status = MonitoredTxStatusMined + if mTx.lastReceipt.Status == ethTypes.ReceiptStatusSuccessful { + mTx.Status = types.MonitoredTxStatusMined mTx.BlockNumber = mTx.lastReceipt.BlockNumber logger.Info("mined") } else { @@ -708,13 +708,13 @@ func (c *Client) monitorTx(ctx context.Context, mTx *monitoredTxnIteration, logg return } // otherwise we understand this monitored tx has failed - mTx.Status = MonitoredTxStatusFailed + mTx.Status = types.MonitoredTxStatusFailed mTx.BlockNumber = mTx.lastReceipt.BlockNumber logger.Info("failed") } // update monitored tx changes into storage - err = c.storage.Update(ctx, *mTx.monitoredTx) + err = c.storage.Update(ctx, *mTx.MonitoredTx) if err != nil { logger.Errorf("failed to update monitored tx: %v", err) return @@ -723,7 +723,7 @@ func (c *Client) monitorTx(ctx context.Context, mTx *monitoredTxnIteration, logg // shouldContinueToMonitorThisTx checks the the tx receipt and decides if it should // continue or not to monitor the monitored tx related to the tx from this receipt -func (c *Client) shouldContinueToMonitorThisTx(ctx context.Context, receipt *types.Receipt) bool { +func (c *Client) shouldContinueToMonitorThisTx(ctx context.Context, receipt *ethTypes.Receipt) bool { // if the receipt has a is successful result, stop monitoring if receipt.Status == ethTypes.ReceiptStatusSuccessful { return false @@ -845,7 +845,7 @@ func (c *Client) reviewMonitoredTxGas(ctx context.Context, mTx *monitoredTxnIter mTx.Gas = gas } - err = c.storage.Update(ctx, *mTx.monitoredTx) + err = c.storage.Update(ctx, *mTx.MonitoredTx) if err != nil { return fmt.Errorf("failed to update monitored tx changes: %w", err) } @@ -855,7 +855,8 @@ func (c *Client) reviewMonitoredTxGas(ctx context.Context, mTx *monitoredTxnIter // getMonitoredTxnIteration gets all monitored txs that need to be sent or resent in current monitor iteration func (c *Client) getMonitoredTxnIteration(ctx context.Context) ([]*monitoredTxnIteration, error) { - txsToUpdate, err := c.storage.GetByStatus(ctx, []MonitoredTxStatus{MonitoredTxStatusCreated, MonitoredTxStatusSent}) + txsToUpdate, err := c.storage.GetByStatus(ctx, + []types.MonitoredTxStatus{types.MonitoredTxStatusCreated, types.MonitoredTxStatusSent}) if err != nil { return nil, fmt.Errorf("failed to get txs to update nonces: %w", err) } @@ -866,7 +867,7 @@ func (c *Client) getMonitoredTxnIteration(ctx context.Context) ([]*monitoredTxnI for _, tx := range txsToUpdate { tx := tx - iteration := &monitoredTxnIteration{monitoredTx: &tx} + iteration := &monitoredTxnIteration{MonitoredTx: &tx} iterations = append(iterations, iteration) updateNonce := iteration.shouldUpdateNonce(ctx, c.etherman) diff --git a/ethtxmanager/ethtxmanager_test.go b/ethtxmanager/ethtxmanager_test.go index 7452dd4..5598723 100644 --- a/ethtxmanager/ethtxmanager_test.go +++ b/ethtxmanager/ethtxmanager_test.go @@ -3,11 +3,16 @@ package ethtxmanager import ( context "context" "errors" + "math/big" "testing" + "time" + localCommon "github.com/0xPolygon/zkevm-ethtx-manager/common" + "github.com/0xPolygon/zkevm-ethtx-manager/ethtxmanager/sqlstorage" "github.com/0xPolygon/zkevm-ethtx-manager/mocks" + "github.com/0xPolygon/zkevm-ethtx-manager/types" common "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -15,7 +20,8 @@ import ( func TestGetMonitoredTxnIteration(t *testing.T) { ctx := context.Background() etherman := mocks.NewEthermanInterface(t) - storage := NewMemStorage("") + storage, err := sqlstorage.NewStorage(localCommon.SQLLiteDriverName, ":memory:") + require.NoError(t, err) client := &Client{ etherman: etherman, @@ -24,7 +30,7 @@ func TestGetMonitoredTxnIteration(t *testing.T) { tests := []struct { name string - storageTxn *monitoredTx + storageTxn *types.MonitoredTx ethermanNonce uint64 shouldUpdate bool expectedResult []*monitoredTxnIteration @@ -37,10 +43,11 @@ func TestGetMonitoredTxnIteration(t *testing.T) { }, { name: "Transaction should not update nonce", - storageTxn: &monitoredTx{ - ID: common.HexToHash("0x1"), - From: common.HexToAddress("0x1"), - Status: MonitoredTxStatusSent, + storageTxn: &types.MonitoredTx{ + ID: common.HexToHash("0x1"), + From: common.HexToAddress("0x1"), + BlockNumber: big.NewInt(10), + Status: types.MonitoredTxStatusSent, History: map[common.Hash]bool{ common.HexToHash("0x1"): true, }, @@ -48,36 +55,39 @@ func TestGetMonitoredTxnIteration(t *testing.T) { shouldUpdate: false, expectedResult: []*monitoredTxnIteration{ { - monitoredTx: &monitoredTx{ - ID: common.HexToHash("0x1"), - From: common.HexToAddress("0x1"), - Status: MonitoredTxStatusSent, + MonitoredTx: &types.MonitoredTx{ + ID: common.HexToHash("0x1"), + From: common.HexToAddress("0x1"), + BlockNumber: big.NewInt(10), + Status: types.MonitoredTxStatusSent, History: map[common.Hash]bool{ common.HexToHash("0x1"): true, }, }, confirmed: true, - lastReceipt: &types.Receipt{Status: types.ReceiptStatusSuccessful}, + lastReceipt: ðtypes.Receipt{Status: ethtypes.ReceiptStatusSuccessful}, }, }, expectedError: nil, }, { name: "Transaction should update nonce", - storageTxn: &monitoredTx{ - ID: common.HexToHash("0x1"), - From: common.HexToAddress("0x1"), - Status: MonitoredTxStatusCreated, + storageTxn: &types.MonitoredTx{ + ID: common.HexToHash("0x1"), + From: common.HexToAddress("0x1"), + Status: types.MonitoredTxStatusCreated, + BlockNumber: big.NewInt(10), }, shouldUpdate: true, ethermanNonce: 1, expectedResult: []*monitoredTxnIteration{ { - monitoredTx: &monitoredTx{ - ID: common.HexToHash("0x1"), - From: common.HexToAddress("0x1"), - Status: MonitoredTxStatusCreated, - Nonce: 1, + MonitoredTx: &types.MonitoredTx{ + ID: common.HexToHash("0x1"), + From: common.HexToAddress("0x1"), + Status: types.MonitoredTxStatusCreated, + Nonce: 1, + BlockNumber: big.NewInt(10), }, }, }, @@ -85,10 +95,11 @@ func TestGetMonitoredTxnIteration(t *testing.T) { }, { name: "Error getting pending nonce", - storageTxn: &monitoredTx{ - ID: common.HexToHash("0x1"), - From: common.HexToAddress("0x1"), - Status: MonitoredTxStatusCreated, + storageTxn: &types.MonitoredTx{ + ID: common.HexToHash("0x1"), + From: common.HexToAddress("0x1"), + Status: types.MonitoredTxStatusCreated, + BlockNumber: big.NewInt(10), }, shouldUpdate: true, expectedError: errors.New("failed to get pending nonce for sender: 0x1. Error: some error"), @@ -97,9 +108,9 @@ func TestGetMonitoredTxnIteration(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - storage.Transactions = make(map[common.Hash]monitoredTx, 1) + require.NoError(t, storage.Empty(ctx)) if tt.storageTxn != nil { - storage.Transactions[tt.storageTxn.ID] = *tt.storageTxn + require.NoError(t, storage.Add(ctx, *tt.storageTxn)) } etherman.ExpectedCalls = nil @@ -116,14 +127,19 @@ func TestGetMonitoredTxnIteration(t *testing.T) { require.ErrorContains(t, err, tt.expectedError.Error()) } else { require.NoError(t, err) - require.Equal(t, tt.expectedResult, result) + if len(tt.expectedResult) > 0 { + require.Len(t, result, len(tt.expectedResult)) + compareTxsWithoutDates(t, *tt.expectedResult[0].MonitoredTx, *result[0].MonitoredTx) + } else { + require.Empty(t, result) + } // now check from storage if len(tt.expectedResult) > 0 { - dbTxns, err := storage.GetByStatus(ctx, []MonitoredTxStatus{tt.storageTxn.Status}) + dbTxns, err := storage.GetByStatus(ctx, []types.MonitoredTxStatus{tt.storageTxn.Status}) require.NoError(t, err) require.Len(t, dbTxns, 1) - require.Equal(t, tt.expectedResult[0].monitoredTx.Nonce, dbTxns[0].Nonce) + require.Equal(t, tt.expectedResult[0].MonitoredTx.Nonce, dbTxns[0].Nonce) } } @@ -131,3 +147,15 @@ func TestGetMonitoredTxnIteration(t *testing.T) { }) } } + +// compareTxsWithout dates compares the two MonitoredTx instances, but without dates, since some functions are altering it +func compareTxsWithoutDates(t *testing.T, expected, actual types.MonitoredTx) { + t.Helper() + + expected.CreatedAt = time.Time{} + expected.UpdatedAt = time.Time{} + actual.CreatedAt = time.Time{} + actual.UpdatedAt = time.Time{} + + require.Equal(t, expected, actual) +} diff --git a/ethtxmanager/monitored_tx_iteration.go b/ethtxmanager/monitored_tx_iteration.go new file mode 100644 index 0000000..d82d43d --- /dev/null +++ b/ethtxmanager/monitored_tx_iteration.go @@ -0,0 +1,75 @@ +package ethtxmanager + +import ( + "context" + + "github.com/0xPolygon/zkevm-ethtx-manager/types" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +type monitoredTxnIteration struct { + *types.MonitoredTx + confirmed bool + lastReceipt *ethtypes.Receipt +} + +func (m *monitoredTxnIteration) shouldUpdateNonce(ctx context.Context, etherman types.EthermanInterface) bool { + if m.Status == types.MonitoredTxStatusCreated { + // transaction was not sent, so no need to check if it was mined + // we need to update the nonce in this case + return true + } + + // check if any of the txs in the history was confirmed + var lastReceiptChecked *ethtypes.Receipt + // monitored tx is confirmed until we find a successful receipt + confirmed := false + // monitored tx doesn't have a failed receipt until we find a failed receipt for any + // tx in the monitored tx history + hasFailedReceipts := false + // all history txs are considered mined until we can't find a receipt for any + // tx in the monitored tx history + allHistoryTxsWereMined := true + for txHash := range m.History { + mined, receipt, err := etherman.CheckTxWasMined(ctx, txHash) + if err != nil { + continue + } + + // if the tx is not mined yet, check that not all the tx were mined and go to the next + if !mined { + allHistoryTxsWereMined = false + continue + } + + lastReceiptChecked = receipt + + // if the tx was mined successfully we can set it as confirmed and break the loop + if lastReceiptChecked.Status == ethtypes.ReceiptStatusSuccessful { + confirmed = true + break + } + + // if the tx was mined but failed, we continue to consider it was not confirmed + // and set that we have found a failed receipt. This info will be used later + // to check if nonce needs to be reviewed + confirmed = false + hasFailedReceipts = true + } + + m.confirmed = confirmed + m.lastReceipt = lastReceiptChecked + + // we need to check if we need to review the nonce carefully, to avoid sending + // duplicated data to the roll-up and causing an unnecessary trusted state reorg. + // + // if we have failed receipts, this means at least one of the generated txs was mined, + // in this case maybe the current nonce was already consumed(if this is the first iteration + // of this cycle, next iteration might have the nonce already updated by the preivous one), + // then we need to check if there are tx that were not mined yet, if so, we just need to wait + // because maybe one of them will get mined successfully + // + // in case of the monitored tx is not confirmed yet, all tx were mined and none of them were + // mined successfully, we need to review the nonce + return !confirmed && hasFailedReceipts && allHistoryTxsWereMined +} diff --git a/ethtxmanager/sqlstorage/migrations/0001.sql b/ethtxmanager/sqlstorage/migrations/0001.sql index aca7be5..577129a 100644 --- a/ethtxmanager/sqlstorage/migrations/0001.sql +++ b/ethtxmanager/sqlstorage/migrations/0001.sql @@ -14,7 +14,7 @@ CREATE TABLE IF NOT EXISTS monitored_txs ( blob_gas_price TEXT, -- *big.Int gas_tip_cap TEXT, -- *big.Int "status" TEXT NOT NULL, - block_number BIGINT NOT NULL, + block_number BIGINT, history JSONB, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, diff --git a/ethtxmanager/storageinterface.generated.go b/ethtxmanager/storageinterface.generated.go deleted file mode 100644 index 82813a8..0000000 --- a/ethtxmanager/storageinterface.generated.go +++ /dev/null @@ -1,401 +0,0 @@ -// Code generated by mockery v2.45.0. DO NOT EDIT. - -package ethtxmanager - -import ( - context "context" - - common "github.com/ethereum/go-ethereum/common" - - mock "github.com/stretchr/testify/mock" -) - -// StorageInterfaceMock is an autogenerated mock type for the StorageInterface type -type StorageInterfaceMock struct { - mock.Mock -} - -type StorageInterfaceMock_Expecter struct { - mock *mock.Mock -} - -func (_m *StorageInterfaceMock) EXPECT() *StorageInterfaceMock_Expecter { - return &StorageInterfaceMock_Expecter{mock: &_m.Mock} -} - -// Add provides a mock function with given fields: ctx, mTx -func (_m *StorageInterfaceMock) Add(ctx context.Context, mTx monitoredTx) error { - ret := _m.Called(ctx, mTx) - - if len(ret) == 0 { - panic("no return value specified for Add") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, monitoredTx) error); ok { - r0 = rf(ctx, mTx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// StorageInterfaceMock_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add' -type StorageInterfaceMock_Add_Call struct { - *mock.Call -} - -// Add is a helper method to define mock.On call -// - ctx context.Context -// - mTx monitoredTx -func (_e *StorageInterfaceMock_Expecter) Add(ctx interface{}, mTx interface{}) *StorageInterfaceMock_Add_Call { - return &StorageInterfaceMock_Add_Call{Call: _e.mock.On("Add", ctx, mTx)} -} - -func (_c *StorageInterfaceMock_Add_Call) Run(run func(ctx context.Context, mTx monitoredTx)) *StorageInterfaceMock_Add_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(monitoredTx)) - }) - return _c -} - -func (_c *StorageInterfaceMock_Add_Call) Return(_a0 error) *StorageInterfaceMock_Add_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *StorageInterfaceMock_Add_Call) RunAndReturn(run func(context.Context, monitoredTx) error) *StorageInterfaceMock_Add_Call { - _c.Call.Return(run) - return _c -} - -// Empty provides a mock function with given fields: ctx -func (_m *StorageInterfaceMock) Empty(ctx context.Context) error { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Empty") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// StorageInterfaceMock_Empty_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Empty' -type StorageInterfaceMock_Empty_Call struct { - *mock.Call -} - -// Empty is a helper method to define mock.On call -// - ctx context.Context -func (_e *StorageInterfaceMock_Expecter) Empty(ctx interface{}) *StorageInterfaceMock_Empty_Call { - return &StorageInterfaceMock_Empty_Call{Call: _e.mock.On("Empty", ctx)} -} - -func (_c *StorageInterfaceMock_Empty_Call) Run(run func(ctx context.Context)) *StorageInterfaceMock_Empty_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *StorageInterfaceMock_Empty_Call) Return(_a0 error) *StorageInterfaceMock_Empty_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *StorageInterfaceMock_Empty_Call) RunAndReturn(run func(context.Context) error) *StorageInterfaceMock_Empty_Call { - _c.Call.Return(run) - return _c -} - -// Get provides a mock function with given fields: ctx, id -func (_m *StorageInterfaceMock) Get(ctx context.Context, id common.Hash) (monitoredTx, error) { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for Get") - } - - var r0 monitoredTx - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, common.Hash) (monitoredTx, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, common.Hash) monitoredTx); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Get(0).(monitoredTx) - } - - if rf, ok := ret.Get(1).(func(context.Context, common.Hash) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// StorageInterfaceMock_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type StorageInterfaceMock_Get_Call struct { - *mock.Call -} - -// Get is a helper method to define mock.On call -// - ctx context.Context -// - id common.Hash -func (_e *StorageInterfaceMock_Expecter) Get(ctx interface{}, id interface{}) *StorageInterfaceMock_Get_Call { - return &StorageInterfaceMock_Get_Call{Call: _e.mock.On("Get", ctx, id)} -} - -func (_c *StorageInterfaceMock_Get_Call) Run(run func(ctx context.Context, id common.Hash)) *StorageInterfaceMock_Get_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(common.Hash)) - }) - return _c -} - -func (_c *StorageInterfaceMock_Get_Call) Return(_a0 monitoredTx, _a1 error) *StorageInterfaceMock_Get_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *StorageInterfaceMock_Get_Call) RunAndReturn(run func(context.Context, common.Hash) (monitoredTx, error)) *StorageInterfaceMock_Get_Call { - _c.Call.Return(run) - return _c -} - -// GetByBlock provides a mock function with given fields: ctx, fromBlock, toBlock -func (_m *StorageInterfaceMock) GetByBlock(ctx context.Context, fromBlock *uint64, toBlock *uint64) ([]monitoredTx, error) { - ret := _m.Called(ctx, fromBlock, toBlock) - - if len(ret) == 0 { - panic("no return value specified for GetByBlock") - } - - var r0 []monitoredTx - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *uint64, *uint64) ([]monitoredTx, error)); ok { - return rf(ctx, fromBlock, toBlock) - } - if rf, ok := ret.Get(0).(func(context.Context, *uint64, *uint64) []monitoredTx); ok { - r0 = rf(ctx, fromBlock, toBlock) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]monitoredTx) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *uint64, *uint64) error); ok { - r1 = rf(ctx, fromBlock, toBlock) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// StorageInterfaceMock_GetByBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByBlock' -type StorageInterfaceMock_GetByBlock_Call struct { - *mock.Call -} - -// GetByBlock is a helper method to define mock.On call -// - ctx context.Context -// - fromBlock *uint64 -// - toBlock *uint64 -func (_e *StorageInterfaceMock_Expecter) GetByBlock(ctx interface{}, fromBlock interface{}, toBlock interface{}) *StorageInterfaceMock_GetByBlock_Call { - return &StorageInterfaceMock_GetByBlock_Call{Call: _e.mock.On("GetByBlock", ctx, fromBlock, toBlock)} -} - -func (_c *StorageInterfaceMock_GetByBlock_Call) Run(run func(ctx context.Context, fromBlock *uint64, toBlock *uint64)) *StorageInterfaceMock_GetByBlock_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*uint64), args[2].(*uint64)) - }) - return _c -} - -func (_c *StorageInterfaceMock_GetByBlock_Call) Return(_a0 []monitoredTx, _a1 error) *StorageInterfaceMock_GetByBlock_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *StorageInterfaceMock_GetByBlock_Call) RunAndReturn(run func(context.Context, *uint64, *uint64) ([]monitoredTx, error)) *StorageInterfaceMock_GetByBlock_Call { - _c.Call.Return(run) - return _c -} - -// GetByStatus provides a mock function with given fields: ctx, statuses -func (_m *StorageInterfaceMock) GetByStatus(ctx context.Context, statuses []MonitoredTxStatus) ([]monitoredTx, error) { - ret := _m.Called(ctx, statuses) - - if len(ret) == 0 { - panic("no return value specified for GetByStatus") - } - - var r0 []monitoredTx - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []MonitoredTxStatus) ([]monitoredTx, error)); ok { - return rf(ctx, statuses) - } - if rf, ok := ret.Get(0).(func(context.Context, []MonitoredTxStatus) []monitoredTx); ok { - r0 = rf(ctx, statuses) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]monitoredTx) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, []MonitoredTxStatus) error); ok { - r1 = rf(ctx, statuses) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// StorageInterfaceMock_GetByStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByStatus' -type StorageInterfaceMock_GetByStatus_Call struct { - *mock.Call -} - -// GetByStatus is a helper method to define mock.On call -// - ctx context.Context -// - statuses []MonitoredTxStatus -func (_e *StorageInterfaceMock_Expecter) GetByStatus(ctx interface{}, statuses interface{}) *StorageInterfaceMock_GetByStatus_Call { - return &StorageInterfaceMock_GetByStatus_Call{Call: _e.mock.On("GetByStatus", ctx, statuses)} -} - -func (_c *StorageInterfaceMock_GetByStatus_Call) Run(run func(ctx context.Context, statuses []MonitoredTxStatus)) *StorageInterfaceMock_GetByStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]MonitoredTxStatus)) - }) - return _c -} - -func (_c *StorageInterfaceMock_GetByStatus_Call) Return(_a0 []monitoredTx, _a1 error) *StorageInterfaceMock_GetByStatus_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *StorageInterfaceMock_GetByStatus_Call) RunAndReturn(run func(context.Context, []MonitoredTxStatus) ([]monitoredTx, error)) *StorageInterfaceMock_GetByStatus_Call { - _c.Call.Return(run) - return _c -} - -// Remove provides a mock function with given fields: ctx, id -func (_m *StorageInterfaceMock) Remove(ctx context.Context, id common.Hash) error { - ret := _m.Called(ctx, id) - - if len(ret) == 0 { - panic("no return value specified for Remove") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, common.Hash) error); ok { - r0 = rf(ctx, id) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// StorageInterfaceMock_Remove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Remove' -type StorageInterfaceMock_Remove_Call struct { - *mock.Call -} - -// Remove is a helper method to define mock.On call -// - ctx context.Context -// - id common.Hash -func (_e *StorageInterfaceMock_Expecter) Remove(ctx interface{}, id interface{}) *StorageInterfaceMock_Remove_Call { - return &StorageInterfaceMock_Remove_Call{Call: _e.mock.On("Remove", ctx, id)} -} - -func (_c *StorageInterfaceMock_Remove_Call) Run(run func(ctx context.Context, id common.Hash)) *StorageInterfaceMock_Remove_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(common.Hash)) - }) - return _c -} - -func (_c *StorageInterfaceMock_Remove_Call) Return(_a0 error) *StorageInterfaceMock_Remove_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *StorageInterfaceMock_Remove_Call) RunAndReturn(run func(context.Context, common.Hash) error) *StorageInterfaceMock_Remove_Call { - _c.Call.Return(run) - return _c -} - -// Update provides a mock function with given fields: ctx, mTx -func (_m *StorageInterfaceMock) Update(ctx context.Context, mTx monitoredTx) error { - ret := _m.Called(ctx, mTx) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, monitoredTx) error); ok { - r0 = rf(ctx, mTx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// StorageInterfaceMock_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' -type StorageInterfaceMock_Update_Call struct { - *mock.Call -} - -// Update is a helper method to define mock.On call -// - ctx context.Context -// - mTx monitoredTx -func (_e *StorageInterfaceMock_Expecter) Update(ctx interface{}, mTx interface{}) *StorageInterfaceMock_Update_Call { - return &StorageInterfaceMock_Update_Call{Call: _e.mock.On("Update", ctx, mTx)} -} - -func (_c *StorageInterfaceMock_Update_Call) Run(run func(ctx context.Context, mTx monitoredTx)) *StorageInterfaceMock_Update_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(monitoredTx)) - }) - return _c -} - -func (_c *StorageInterfaceMock_Update_Call) Return(_a0 error) *StorageInterfaceMock_Update_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *StorageInterfaceMock_Update_Call) RunAndReturn(run func(context.Context, monitoredTx) error) *StorageInterfaceMock_Update_Call { - _c.Call.Return(run) - return _c -} - -// NewStorageInterfaceMock creates a new instance of StorageInterfaceMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewStorageInterfaceMock(t interface { - mock.TestingT - Cleanup(func()) -}) *StorageInterfaceMock { - mock := &StorageInterfaceMock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/go.sum b/go.sum index 1f7ff45..68f044c 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,8 @@ github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8x github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= +github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= +github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -133,6 +135,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -151,6 +155,8 @@ github.com/rubenv/sql-migrate v1.7.0 h1:HtQq1xyTN2ISmQDggnh0c9U3JlP8apWh8YO2jzlX github.com/rubenv/sql-migrate v1.7.0/go.mod h1:S4wtDEG1CKn+0ShpTtzWhFpHHI5PvCUtiGI+C+Z2THE= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/meddler v1.0.1 h1:JLR7Z4M4iGm1nr7DIURBq18UW8cTrm+qArUFgOhELo8= +github.com/russross/meddler v1.0.1/go.mod h1:GzGDChbFHuzxlFwt8gnJMRRNyFSQDSudmy2kHh7GYnQ= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= diff --git a/types/monitored_tx.go b/types/monitored_tx.go index a1c41a8..36bc030 100644 --- a/types/monitored_tx.go +++ b/types/monitored_tx.go @@ -1,7 +1,6 @@ package types import ( - "context" "database/sql" "math/big" "time" @@ -200,70 +199,3 @@ type TxResult struct { Receipt *types.Receipt RevertMessage string } - -type monitoredTxnIteration struct { - *monitoredTx - confirmed bool - lastReceipt *types.Receipt -} - -func (m *monitoredTxnIteration) shouldUpdateNonce(ctx context.Context, etherman EthermanInterface) bool { - if m.Status == MonitoredTxStatusCreated { - // transaction was not sent, so no need to check if it was mined - // we need to update the nonce in this case - return true - } - - // check if any of the txs in the history was confirmed - var lastReceiptChecked *types.Receipt - // monitored tx is confirmed until we find a successful receipt - confirmed := false - // monitored tx doesn't have a failed receipt until we find a failed receipt for any - // tx in the monitored tx history - hasFailedReceipts := false - // all history txs are considered mined until we can't find a receipt for any - // tx in the monitored tx history - allHistoryTxsWereMined := true - for txHash := range m.History { - mined, receipt, err := etherman.CheckTxWasMined(ctx, txHash) - if err != nil { - continue - } - - // if the tx is not mined yet, check that not all the tx were mined and go to the next - if !mined { - allHistoryTxsWereMined = false - continue - } - - lastReceiptChecked = receipt - - // if the tx was mined successfully we can set it as confirmed and break the loop - if lastReceiptChecked.Status == types.ReceiptStatusSuccessful { - confirmed = true - break - } - - // if the tx was mined but failed, we continue to consider it was not confirmed - // and set that we have found a failed receipt. This info will be used later - // to check if nonce needs to be reviewed - confirmed = false - hasFailedReceipts = true - } - - m.confirmed = confirmed - m.lastReceipt = lastReceiptChecked - - // we need to check if we need to review the nonce carefully, to avoid sending - // duplicated data to the roll-up and causing an unnecessary trusted state reorg. - // - // if we have failed receipts, this means at least one of the generated txs was mined, - // in this case maybe the current nonce was already consumed(if this is the first iteration - // of this cycle, next iteration might have the nonce already updated by the preivous one), - // then we need to check if there are tx that were not mined yet, if so, we just need to wait - // because maybe one of them will get mined successfully - // - // in case of the monitored tx is not confirmed yet, all tx were mined and none of them were - // mined successfully, we need to review the nonce - return !confirmed && hasFailedReceipts && allHistoryTxsWereMined -}