synapsecns/sanguine

View on GitHub
ethergo/submitter/db_test.go

Summary

Maintainability
A
2 hrs
Test Coverage
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))
    })
}