package submitter_test import ( "errors" "github.com/brianvoe/gofakeit/v6" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/google/uuid" "github.com/synapsecns/sanguine/core/testsuite" "github.com/synapsecns/sanguine/ethergo/backends/simulated" "github.com/synapsecns/sanguine/ethergo/mocks" "github.com/synapsecns/sanguine/ethergo/signer/nonce" "github.com/synapsecns/sanguine/ethergo/submitter/db" "math/big" "github.com/synapsecns/sanguine/ethergo/util" ) func (t *TXSubmitterDBSuite) TestGetNonceForChainID() { t.RunOnAllDBs(func(testDB db.Service) { for _, backend := range t.testBackends { manager := t.managers[backend.GetChainID()] for _, mockAccount := range t.mockAccounts { // make sure first nonce errors _, err := testDB.GetNonceForChainID(t.GetTestContext(), mockAccount.Address, backend.GetBigChainID()) t.Require().True(errors.Is(err, db.ErrNoNonceForChain)) for i := 0; i < 4; i++ { tx, err := manager.SignTx(types.NewTx(&types.LegacyTx{ To: &mockAccount.Address, Value: new(big.Int).SetUint64(gofakeit.Uint64()), }), backend.Signer(), mockAccount.PrivateKey) t.Require().NoError(err) // make sure nonces are correct err = testDB.PutTXS(t.GetTestContext(), db.TX{ Transaction: tx, Status: db.Pending, }) t.Require().NoError(err) dbNonce, err := testDB.GetNonceForChainID(t.GetTestContext(), mockAccount.Address, backend.GetBigChainID()) t.Require().NoError(err) t.Require().Equal(dbNonce, tx.Nonce()) } } } distinctChains, err := testDB.GetDistinctChainIDs(t.GetTestContext()) t.Require().NoError(err) t.Require().Equal(len(t.testBackends), len(distinctChains)) }) } func (t *TXSubmitterDBSuite) TestGetTransactionsWithLimitPerChainID() { t.RunOnAllDBs(func(testDB db.Service) { for _, backend := range t.testBackends { manager := t.managers[backend.GetChainID()] for _, mockAccount := range t.mockAccounts { // create some test transactions var txs []*types.Transaction for i := 0; i < 50; i++ { legacyTx := &types.LegacyTx{ To: &mockAccount.Address, Value: big.NewInt(0), Nonce: uint64(i), } tx, err := manager.SignTx(types.NewTx(legacyTx), backend.Signer(), mockAccount.PrivateKey) t.Require().NoError(err) txs = append(txs, tx) } // put the transactions in the database for _, tx := range txs { err := testDB.PutTXS(t.GetTestContext(), db.NewTX(tx, db.Pending, uuid.New().String())) t.Require().NoError(err) // add a copy of the tx w/ a hardcoded gas price we can use to identify the created at time. This should be returned since it's // the latest created at copiedTx, err := util.CopyTX(tx, util.WithGasPrice(big.NewInt(1))) t.Require().NoError(err) // sign it copiedTx, err = manager.SignTx(copiedTx, backend.Signer(), mockAccount.PrivateKey, nonce.WithNoBump(true)) t.Require().NoError(err) err = testDB.PutTXS(t.GetTestContext(), db.NewTX(copiedTx, db.Pending, uuid.New().String())) t.Require().NoError(err) } // get the transactions with limit per ChainID result, err := testDB.GetTXS(t.GetTestContext(), mockAccount.Address, backend.GetBigChainID(), db.WithStatuses(db.Pending)) t.Require().NoError(err) // check that the result has the correct length t.Require().Equal(db.DefaultMaxResultsPerChain, len(result)) // check that the result is limited per ChainID and address for _, tx := range result { t.Require().Equal(backend.GetBigChainID(), tx.ChainId(), testsuite.BigIntComparer()) msg, err := util.TxToCall(tx.Transaction) t.Require().NoError(err) t.Require().Equal(mockAccount.Address, msg.From) } // check that the result is ordered by nonce for i := 0; i < len(result)-1; i++ { t.Require().Less(result[i].Nonce(), result[i+1].Nonce()) // make sure the gas price is correct and the most recently created has been fetched t.Require().Equal(result[i].GasPrice(), big.NewInt(1), testsuite.BigIntComparer()) } // make sure this returns double the number of results, 2 per tx // TODO: check nonces result, err = testDB.GetAllTXAttemptByStatus(t.GetTestContext(), mockAccount.Address, backend.GetBigChainID(), db.WithStatuses(db.Pending)) t.Require().NoError(err) t.Require().Equal(db.DefaultMaxResultsPerChain*2, len(result)) } } }) } func (t *TXSubmitterDBSuite) TestGetNonceStatus() { t.RunOnAllDBs(func(dbs db.Service) { _, err := dbs.GetNonceStatus(t.GetTestContext(), mocks.MockAddress(), big.NewInt(1), 4) t.Require().ErrorIs(err, db.ErrNonceNotExist) simulatedBackend := simulated.NewSimulatedBackend(t.GetTestContext(), t.T()) acct := simulatedBackend.GetFundedAccount(t.GetTestContext(), big.NewInt(params.Ether)) mockTx := mocks.MockTx(t.GetTestContext(), t.T(), simulatedBackend, acct, types.LegacyTxType) for i, status := range db.AllStatusTypes() { copiedTX, err := util.CopyTX(mockTx, util.WithGasPrice(big.NewInt(int64(i)))) t.Require().NoError(err) copiedTX, err = types.SignTx(copiedTX, simulatedBackend.Signer(), acct.PrivateKey) t.Require().NoError(err) err = dbs.PutTXS(t.GetTestContext(), db.TX{ Transaction: copiedTX, Status: status, }) t.Require().NoError(err) msg, err := util.TxToCall(copiedTX) t.Require().NoError(err) nonceStatus, err := dbs.GetNonceStatus(t.GetTestContext(), msg.From, simulatedBackend.GetBigChainID(), mockTx.Nonce()) t.Require().NoError(err) t.Require().Equal(status, nonceStatus) txs, err := dbs.GetNonceAttemptsByStatus(t.GetTestContext(), msg.From, simulatedBackend.GetBigChainID(), mockTx.Nonce(), status) t.Require().NoError(err) t.Require().Equal(1, len(txs)) } }) } func (t *TXSubmitterDBSuite) TestGetChainIDsByStatus() { t.RunOnAllDBs(func(testDB db.Service) { chainIDToStatus := map[int64]db.Status{ 1: db.Pending, 3: db.Stored, 4: db.FailedSubmit, } expectedPendingChainIDs := []int64{1} for _, mockAccount := range t.mockAccounts { for _, backend := range t.testBackends { manager := t.managers[backend.GetChainID()] // create some test transactions var txs []*types.Transaction for i := 0; i < 50; i++ { legacyTx := &types.LegacyTx{ To: &mockAccount.Address, Value: big.NewInt(0), Nonce: uint64(i), } tx, err := manager.SignTx(types.NewTx(legacyTx), backend.Signer(), mockAccount.PrivateKey) t.Require().NoError(err) txs = append(txs, tx) } // put the transactions in the database for _, tx := range txs { err := testDB.PutTXS(t.GetTestContext(), db.NewTX(tx, chainIDToStatus[backend.GetBigChainID().Int64()], uuid.New().String())) t.Require().NoError(err) } } // check which chainIDs are stored with pending status result, err := testDB.GetChainIDsByStatus(t.GetTestContext(), mockAccount.Address, db.Pending) t.Require().NoError(err) resultInt64 := make([]int64, len(result)) for i, chainID := range result { resultInt64[i] = chainID.Int64() } t.Equal(expectedPendingChainIDs, resultInt64) } }) } func (t *TXSubmitterDBSuite) TestDeleteTXS() { t.RunOnAllDBs(func(testDB db.Service) { nonce := uint64(0) mockAccount := t.mockAccounts[0] backend := t.testBackends[0] storeTx := func(status db.Status) { manager := t.managers[backend.GetChainID()] legacyTx := &types.LegacyTx{ To: &mockAccount.Address, Value: big.NewInt(0), Nonce: nonce, } tx, err := manager.SignTx(types.NewTx(legacyTx), backend.Signer(), mockAccount.PrivateKey) t.Require().NoError(err) dbTx := db.NewTX(tx, status, uuid.New().String()) err = testDB.PutTXS(t.GetTestContext(), dbTx) t.Require().NoError(err) nonce++ } storeTx(db.Pending) storeTx(db.Stored) storeTx(db.Replaced) storeTx(db.ReplacedOrConfirmed) storeTx(db.Confirmed) // ensure txs were stored allStatuses := []db.Status{db.Pending, db.Stored, db.Replaced, db.ReplacedOrConfirmed, db.Confirmed} txs, err := testDB.GetTXS(t.GetTestContext(), mockAccount.Address, backend.GetBigChainID(), db.WithStatuses(allStatuses...)) t.Require().NoError(err) t.Equal(5, len(txs)) // delete non-terminal txs err = testDB.DeleteTXS(t.GetTestContext(), 0, db.Replaced, db.ReplacedOrConfirmed, db.Confirmed) t.Require().NoError(err) // ensure txs were deleted txs, err = testDB.GetTXS(t.GetTestContext(), mockAccount.Address, backend.GetBigChainID(), db.WithStatuses(allStatuses...)) t.Require().NoError(err) t.Equal(2, len(txs)) }) }