package submitter_test import ( "context" "fmt" "math/big" "math/rand" "sort" "sync" "testing" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/google/go-cmp/cmp" "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/testsuite" "github.com/synapsecns/sanguine/ethergo/backends/simulated" "github.com/synapsecns/sanguine/ethergo/mocks" "github.com/synapsecns/sanguine/ethergo/submitter" "github.com/synapsecns/sanguine/ethergo/submitter/db" "github.com/synapsecns/sanguine/ethergo/util" "gotest.tools/assert" ) func TestCopyTransactOpts(t *testing.T) { // Test case 1: All fields populated opts1 := &bind.TransactOpts{ From: common.HexToAddress("0x1234567890123456789012345678901234567890"), Nonce: big.NewInt(1), Signer: nil, Value: big.NewInt(100), GasPrice: big.NewInt(200), GasFeeCap: big.NewInt(300), GasTipCap: big.NewInt(400), GasLimit: 500, Context: context.Background(), NoSend: true, } copyOpts1 := submitter.CopyTransactOpts(opts1) assertTransactOptsEquality(t, opts1, copyOpts1) // Test case 2: Some fields populated, others nil opts2 := &bind.TransactOpts{ Nonce: nil, Signer: nil, Value: nil, GasPrice: nil, GasFeeCap: big.NewInt(300), GasTipCap: big.NewInt(400), GasLimit: 500, Context: context.Background(), NoSend: true, } copyOpts2 := submitter.CopyTransactOpts(opts2) assertTransactOptsEquality(t, opts2, copyOpts2) // Test case 3: All fields nil opts3 := &bind.TransactOpts{ Nonce: nil, Signer: nil, Value: nil, GasPrice: nil, GasFeeCap: nil, GasTipCap: nil, GasLimit: 0, Context: nil, NoSend: true, } copyOpts3 := submitter.CopyTransactOpts(opts3) assertTransactOptsEquality(t, opts3, copyOpts3) } func assertTransactOptsEquality(tb testing.TB, toA, toB *bind.TransactOpts) { tb.Helper() // Check that the pointer values of the big integer fields are different assertBigIntsCopiedEqual(tb, toA.Nonce, toB.Nonce, "Nonce") assertBigIntsCopiedEqual(tb, toA.Value, toB.Value, "Value") assertBigIntsCopiedEqual(tb, toA.GasPrice, toB.GasPrice, "GasPrice") assertBigIntsCopiedEqual(tb, toA.GasFeeCap, toB.GasFeeCap, "GasFeeCap") assertBigIntsCopiedEqual(tb, toA.GasTipCap, toB.GasTipCap, "GasFeeCap") assert.DeepEqual(tb, toA, toB, testsuite.BigIntComparer(), cmp.AllowUnexported(context.Background())) } // assertBigIntsCopiedEqual checks that the given big.Ints are equal and that // they have different pointers. func assertBigIntsCopiedEqual(tb testing.TB, original *big.Int, newVal *big.Int, fieldName string) { tb.Helper() if original == nil && newVal == nil { return } if core.ArePointersEqual(original, newVal) { tb.Errorf("%s has same pointer as original", fieldName) } if original.Cmp(newVal) != 0 { tb.Errorf("%s is not equal", fieldName) } } func (s *SubmitterSuite) TestSortTxes() { expected := make(map[uint64][]*types.Transaction) var allTxes []db.TX var mapMux sync.Mutex var sliceMux sync.Mutex chainIDS := []int64{1, 2, 3, 4, 5} var wg sync.WaitGroup wg.Add(len(chainIDS)) for i := range chainIDS { chainID := big.NewInt(chainIDS[i]) go func() { defer wg.Done() backend := simulated.NewSimulatedBackendWithChainID(s.GetTestContext(), s.T(), chainID) testAddress := backend.GetTxContext(s.GetTestContext(), nil) testKey := &keystore.Key{PrivateKey: testAddress.PrivateKey, Address: testAddress.From} for i := 0; i < 50; i++ { mockTX := mocks.MockTx(s.GetTestContext(), s.T(), backend, testKey, types.DynamicFeeTxType) // add to map in order mapMux.Lock() expected[chainID.Uint64()] = append(expected[chainID.Uint64()], mockTX) mapMux.Unlock() sliceMux.Lock() tx := db.TX{ Transaction: mockTX, Status: db.Stored, } tx.UnsafeSetCreationTime(time.Now()) allTxes = append(allTxes, tx) // shuffle the slice each time rand.Shuffle(len(allTxes), func(i, j int) { allTxes[i], allTxes[j] = allTxes[j], allTxes[i] }) sliceMux.Unlock() } }() } wg.Wait() sorted := submitter.SortTxes(allTxes, 50) assert.Equal(s.T(), len(sorted), len(expected)) for chainID, txes := range expected { for i := range txes { assert.Equal(s.T(), sorted[chainID][i].Hash(), txes[i].Hash()) } } // check tx cap numTxes := 10 sorted = submitter.SortTxes(allTxes, numTxes) assert.Equal(s.T(), len(sorted), len(expected)) for chainID, txes := range expected { chainTxes := txes[:numTxes] for i := range chainTxes { assert.Equal(s.T(), sorted[chainID][i].Hash(), txes[i].Hash()) } } } func (s *SubmitterSuite) TestGroupTxesByNonce() { ogTx := mocks.GetMockTxes(s.GetTestContext(), s.T(), 1, types.LegacyTxType)[0] var txes []db.TX // generate 1,000 txes with 100 different nonces for nonce := 0; nonce < 100; nonce++ { copiedTX, err := util.CopyTX(ogTx, util.WithNonce(uint64(nonce))) s.Require().NoError(err) for i := 0; i < 10; i++ { newTX, err := util.CopyTX(copiedTX, util.WithGasPrice(big.NewInt(int64(i)))) s.Require().NoError(err) txes = append(txes, db.TX{ Transaction: newTX, Status: db.Pending, }) } } nonceMap := submitter.GroupTxesByNonce(txes) for i := 0; i < 100; i++ { txList := nonceMap[uint64(i)] for _, tx := range txList { if tx.Nonce() != uint64(i) { s.Require().NoError(fmt.Errorf("expected nonce %d, got %d", i, tx.Nonce())) } } } } // Test for the outersection function. func TestOutersection(t *testing.T) { set := []*big.Int{ big.NewInt(2), big.NewInt(4), } superset := []*big.Int{ big.NewInt(1), big.NewInt(2), big.NewInt(3), big.NewInt(4), big.NewInt(5), } expected := []*big.Int{ big.NewInt(1), big.NewInt(3), big.NewInt(5), } result := submitter.Outersection(set, superset) if len(result) != len(expected) { t.Fatalf("Expected %d elements, but got %d", len(expected), len(result)) } for i, v := range result { if v.Cmp(expected[i]) != 0 { t.Errorf("Expected %s but got %s at index %d", expected[i], v, i) } } } // bigIntSlice is a type for sorting []*big.Int. type bigIntSlice []*big.Int func (p bigIntSlice) Len() int { return len(p) } func (p bigIntSlice) Less(i, j int) bool { return p[i].Cmp(p[j]) < 0 } func (p bigIntSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // Test for the MapToBigIntSlice function with generics. func TestMapToBigIntSlice(t *testing.T) { m := map[uint64]struct{}{ 1: {}, 2: {}, 3: {}, } expected := []*big.Int{ big.NewInt(1), big.NewInt(2), big.NewInt(3), } result := submitter.MapToBigIntSlice(m) if len(result) != len(expected) { t.Fatalf("Expected %d elements, but got %d", len(expected), len(result)) } sort.Sort(bigIntSlice(result)) sort.Sort(bigIntSlice(expected)) for i, v := range result { if v.Cmp(expected[i]) != 0 { t.Errorf("Expected %s but got %s at index %d", expected[i], v, i) } } } func TestMapToBigIntSliceWithStruct(t *testing.T) { type MyStruct struct { Value int } m := map[uint64]MyStruct{ 1: {Value: 10}, 2: {Value: 20}, 3: {Value: 30}, } expected := []*big.Int{ big.NewInt(1), big.NewInt(2), big.NewInt(3), } result := submitter.MapToBigIntSlice(m) if len(result) != len(expected) { t.Fatalf("Expected %d elements, but got %d", len(expected), len(result)) } sort.Sort(bigIntSlice(result)) sort.Sort(bigIntSlice(expected)) for i, v := range result { if v.Cmp(expected[i]) != 0 { t.Errorf("Expected %s but got %s at index %d", expected[i], v, i) } } }