diff --git a/ethergo/submitter/export_test.go b/ethergo/submitter/export_test.go index 3eb3d476ce..9160fd7eba 100644 --- a/ethergo/submitter/export_test.go +++ b/ethergo/submitter/export_test.go @@ -12,7 +12,6 @@ import ( "github.com/synapsecns/sanguine/ethergo/signer/signer" "github.com/synapsecns/sanguine/ethergo/submitter/config" "github.com/synapsecns/sanguine/ethergo/submitter/db" - "go.opentelemetry.io/otel/attribute" ) // CopyTransactOpts exports copyTransactOpts for testing. @@ -20,24 +19,6 @@ func CopyTransactOpts(opts *bind.TransactOpts) *bind.TransactOpts { return copyTransactOpts(opts) } -// NullFieldAttribute is a constant used to test the null field attribute. -// it exports the underlying constant for testing. -const NullFieldAttribute = nullFieldAttribute - -func AddressPtrToString(address *common.Address) string { - return addressPtrToString(address) -} - -// BigPtrToString converts a big.Int pointer to a string. -func BigPtrToString(num *big.Int) string { - return bigPtrToString(num) -} - -// TxToAttributes exports txToAttributes for testing. -func TxToAttributes(transaction *types.Transaction, UUID string) []attribute.KeyValue { - return txToAttributes(transaction, UUID) -} - // SortTxes exports sortTxesByChainID for testing. func SortTxes(txs []db.TX, maxPerChain int) map[uint64][]db.TX { return sortTxesByChainID(txs, maxPerChain) @@ -48,31 +29,6 @@ func GroupTxesByNonce(txs []db.TX) map[uint64][]db.TX { return groupTxesByNonce(txs) } -const ( - // HashAttr exports hashAttr for testing. - HashAttr = hashAttr - // FromAttr exports fromAttr for testing. - FromAttr = fromAttr - // ToAttr exports toAttr for testing. - ToAttr = toAttr - // DataAttr exports dataAttr for testing. - DataAttr = dataAttr - // ValueAttr exports valueAttr for testing. - ValueAttr = valueAttr - // NonceAttr exports nonceAttr for testing. - NonceAttr = nonceAttr - // GasLimitAttr exports gasLimitAttr for testing. - GasLimitAttr = gasLimitAttr - // ChainIDAttr exports chainIDAttr for testing. - ChainIDAttr = chainIDAttr - // GasPriceAttr exports gasPriceAttr for testing. - GasPriceAttr = gasPriceAttr - // GasFeeCapAttr exports gasFeeCapAttr for testing. - GasFeeCapAttr = gasFeeCapAttr - // GasTipCapAttr exports gasTipCapAttr for testing. - GasTipCapAttr = gasTipCapAttr -) - // NewTestTransactionSubmitter wraps TestTransactionSubmitter in a TransactionSubmitter interface. func NewTestTransactionSubmitter(metrics metrics.Handler, signer signer.Signer, fetcher ClientFetcher, db db.Service, config *config.Config) TestTransactionSubmitter { txSubmitter := NewTransactionSubmitter(metrics, signer, fetcher, db, config) diff --git a/ethergo/submitter/submitter.go b/ethergo/submitter/submitter.go index 2d0a781688..2eaec50f64 100644 --- a/ethergo/submitter/submitter.go +++ b/ethergo/submitter/submitter.go @@ -463,9 +463,9 @@ func (t *txSubmitterImpl) setGasPrice(ctx context.Context, client client.EVM, span.SetAttributes( attribute.Int(metrics.ChainID, chainID), attribute.Bool("use_dynamic", useDynamic), - attribute.String("gas_price", bigPtrToString(transactor.GasPrice)), - attribute.String("gas_fee_cap", bigPtrToString(transactor.GasFeeCap)), - attribute.String("gas_tip_cap", bigPtrToString(transactor.GasTipCap)), + attribute.String("gas_price", util.BigPtrToString(transactor.GasPrice)), + attribute.String("gas_fee_cap", util.BigPtrToString(transactor.GasFeeCap)), + attribute.String("gas_tip_cap", util.BigPtrToString(transactor.GasTipCap)), ) metrics.EndSpanWithErr(span, err) }() @@ -501,9 +501,9 @@ func (t *txSubmitterImpl) bumpGasFromPrevTx(ctx context.Context, transactor *bin defer func() { span.SetAttributes( - attribute.String("gas_price", bigPtrToString(transactor.GasPrice)), - attribute.String("gas_fee_cap", bigPtrToString(transactor.GasFeeCap)), - attribute.String("gas_tip_cap", bigPtrToString(transactor.GasTipCap)), + attribute.String("gas_price", util.BigPtrToString(transactor.GasPrice)), + attribute.String("gas_fee_cap", util.BigPtrToString(transactor.GasFeeCap)), + attribute.String("gas_tip_cap", util.BigPtrToString(transactor.GasTipCap)), ) metrics.EndSpan(span) }() @@ -537,9 +537,9 @@ func (t *txSubmitterImpl) applyGasFloor(ctx context.Context, transactor *bind.Tr defer func() { span.SetAttributes( - attribute.String("gas_price", bigPtrToString(transactor.GasPrice)), - attribute.String("gas_fee_cap", bigPtrToString(transactor.GasFeeCap)), - attribute.String("gas_tip_cap", bigPtrToString(transactor.GasTipCap)), + attribute.String("gas_price", util.BigPtrToString(transactor.GasPrice)), + attribute.String("gas_fee_cap", util.BigPtrToString(transactor.GasFeeCap)), + attribute.String("gas_tip_cap", util.BigPtrToString(transactor.GasTipCap)), ) metrics.EndSpan(span) }() @@ -578,10 +578,10 @@ func (t *txSubmitterImpl) applyGasFromOracle(ctx context.Context, transactor *bi } transactor.GasTipCap = maxOfBig(transactor.GasTipCap, suggestedGasTipCap) span.SetAttributes( - attribute.String("suggested_gas_fee_cap", bigPtrToString(suggestedGasFeeCap)), - attribute.String("suggested_gas_tip_cap", bigPtrToString(suggestedGasTipCap)), - attribute.String("gas_fee_cap", bigPtrToString(transactor.GasFeeCap)), - attribute.String("gas_tip_cap", bigPtrToString(transactor.GasTipCap)), + attribute.String("suggested_gas_fee_cap", util.BigPtrToString(suggestedGasFeeCap)), + attribute.String("suggested_gas_tip_cap", util.BigPtrToString(suggestedGasTipCap)), + attribute.String("gas_fee_cap", util.BigPtrToString(transactor.GasFeeCap)), + attribute.String("gas_tip_cap", util.BigPtrToString(transactor.GasTipCap)), ) } else { suggestedGasPrice, err := client.SuggestGasPrice(ctx) @@ -590,8 +590,8 @@ func (t *txSubmitterImpl) applyGasFromOracle(ctx context.Context, transactor *bi } transactor.GasPrice = maxOfBig(transactor.GasPrice, suggestedGasPrice) span.SetAttributes( - attribute.String("suggested_gas_price", bigPtrToString(suggestedGasPrice)), - attribute.String("gas_price", bigPtrToString(transactor.GasPrice)), + attribute.String("suggested_gas_price", util.BigPtrToString(suggestedGasPrice)), + attribute.String("gas_price", util.BigPtrToString(transactor.GasPrice)), ) } return nil @@ -605,7 +605,7 @@ func (t *txSubmitterImpl) applyGasCeil(ctx context.Context, transactor *bind.Tra maxPrice := t.config.GetMaxGasPrice(chainID) defer func() { - span.SetAttributes(attribute.String("max_price", bigPtrToString(maxPrice))) + span.SetAttributes(attribute.String("max_price", util.BigPtrToString(maxPrice))) metrics.EndSpanWithErr(span, err) }() @@ -661,7 +661,7 @@ func (t *txSubmitterImpl) getGasBlock(ctx context.Context, chainClient client.EV if ok { span.AddEvent("could not get gas block; using cached value", trace.WithAttributes( attribute.String("error", err.Error()), - attribute.String("blockNumber", bigPtrToString(gasBlock.Number)), + attribute.String("blockNumber", util.BigPtrToString(gasBlock.Number)), )) } else { return nil, fmt.Errorf("could not get gas block: %w", err) diff --git a/ethergo/submitter/util.go b/ethergo/submitter/util.go index 63d43c92d4..3df65d6c1f 100644 --- a/ethergo/submitter/util.go +++ b/ethergo/submitter/util.go @@ -1,18 +1,16 @@ package submitter import ( - "fmt" + "github.com/ethereum/go-ethereum/core/types" + "github.com/synapsecns/sanguine/ethergo/util" + "go.opentelemetry.io/otel/attribute" "math/big" "sort" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/ethergo/chain/gas" "github.com/synapsecns/sanguine/ethergo/submitter/db" - "github.com/synapsecns/sanguine/ethergo/util" - "go.opentelemetry.io/otel/attribute" ) // copyTransactOpts creates a deep copy of the given TransactOpts struct @@ -33,74 +31,14 @@ func copyTransactOpts(opts *bind.TransactOpts) *bind.TransactOpts { return copyOpts } -const ( - uuidAttr = "tx.UUID" - hashAttr = "tx.Hash" - fromAttr = "tx.From" - toAttr = "tx.To" - dataAttr = "tx.Data" - valueAttr = "tx.Value" - nonceAttr = "tx.Nonce" - gasLimitAttr = "tx.GasLimit" - chainIDAttr = "tx.ChainID" - gasPriceAttr = "tx.GasPrice" - gasFeeCapAttr = "tx.GasFeeCap" - gasTipCapAttr = "tx.GasTipCap" -) - -// txToAttributes converts a transaction to a slice of attribute.KeyValue. -func txToAttributes(transaction *types.Transaction, uuid string) []attribute.KeyValue { - var from string - call, err := util.TxToCall(transaction) - if err != nil { - from = fmt.Sprintf("could not be detected: %v", err) - } else { - from = call.From.Hex() - } - var attributes = []attribute.KeyValue{ - attribute.String(uuidAttr, uuid), - attribute.String(hashAttr, transaction.Hash().Hex()), - attribute.String(fromAttr, from), - attribute.String(toAttr, addressPtrToString(transaction.To())), - attribute.String(dataAttr, fmt.Sprintf("%x", transaction.Data())), - attribute.String(valueAttr, bigPtrToString(transaction.Value())), - // TODO: this could be downcast to int64, but it's unclear how we should handle overflows. - // since this is only for tracing, we can probably ignore it for now. - attribute.Int64(nonceAttr, int64(transaction.Nonce())), - attribute.Int64(gasLimitAttr, int64(transaction.Gas())), - attribute.String(chainIDAttr, bigPtrToString(transaction.ChainId())), - } - - if transaction.Type() == types.LegacyTxType && transaction.GasPrice() != nil { - attributes = append(attributes, attribute.String(gasPriceAttr, bigPtrToString(transaction.GasPrice()))) - } - - if transaction.Type() == types.DynamicFeeTxType && transaction.GasFeeCap() != nil { - attributes = append(attributes, attribute.String(gasFeeCapAttr, bigPtrToString(transaction.GasFeeCap()))) - } - - if transaction.Type() == types.DynamicFeeTxType && transaction.GasTipCap() != nil { - attributes = append(attributes, attribute.String(gasTipCapAttr, bigPtrToString(transaction.GasTipCap()))) - } +func txToAttributes(transaction *types.Transaction, uuid string) (attributes []attribute.KeyValue) { + attributes = util.TxToAttributes(transaction) + attributes = append(attributes, attribute.String(uuidAttr, uuid)) return attributes } -const nullFieldAttribute = "null" - -func addressPtrToString(address *common.Address) string { - if address == nil { - return nullFieldAttribute - } - return address.Hex() -} - -func bigPtrToString(num *big.Int) string { - if num == nil { - return nullFieldAttribute - } - return num.String() -} +const uuidAttr = "tx.UUID" // sortTxesByChainID sorts a slice of transactions by nonce. func sortTxesByChainID(txs []db.TX, maxPerChain int) map[uint64][]db.TX { diff --git a/ethergo/submitter/util_test.go b/ethergo/submitter/util_test.go index 8e664e155b..a5e057324e 100644 --- a/ethergo/submitter/util_test.go +++ b/ethergo/submitter/util_test.go @@ -15,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/google/go-cmp/cmp" - "github.com/google/uuid" "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/testsuite" "github.com/synapsecns/sanguine/ethergo/backends/simulated" @@ -23,7 +22,6 @@ import ( "github.com/synapsecns/sanguine/ethergo/submitter" "github.com/synapsecns/sanguine/ethergo/submitter/db" "github.com/synapsecns/sanguine/ethergo/util" - "go.opentelemetry.io/otel/attribute" "gotest.tools/assert" ) @@ -106,95 +104,6 @@ func assertBigIntsCopiedEqual(tb testing.TB, original *big.Int, newVal *big.Int, } } -func TestAddressPtrToString(t *testing.T) { - // Test case 1: Address is nil - var address *common.Address - assert.Equal(t, submitter.AddressPtrToString(address), submitter.NullFieldAttribute) - - // Test case 2: Address is not nil - address = core.PtrTo[common.Address](common.HexToAddress("0x1234567890123456789012345678901234567890")) - assert.Equal(t, submitter.AddressPtrToString(address), "0x1234567890123456789012345678901234567890") -} - -func TestBigPtrToString(t *testing.T) { - // Test case: num is nil - var num *big.Int - expected := submitter.NullFieldAttribute - result := submitter.BigPtrToString(num) - if result != expected { - t.Errorf("bigPtrToString(nil) = %q; want %q", result, expected) - } - - // Test case: num is an integer - num = big.NewInt(123) - expected = "123" - result = submitter.BigPtrToString(num) - if result != expected { - t.Errorf("bigPtrToString(123) = %q; want %q", result, expected) - } -} - -func (s *SubmitterSuite) TestTxToAttributesNullFields() { - s.checkEmptyTx(types.NewTx(&types.DynamicFeeTx{})) - s.checkEmptyTx(types.NewTx(&types.LegacyTx{})) -} - -func (s *SubmitterSuite) checkEmptyTx(rawTx *types.Transaction) { - tx := makeAttrMap(rawTx, uuid.New().String()) - - s.Require().Equal(tx[submitter.HashAttr].AsString(), rawTx.Hash().Hex()) - s.Require().Equal(tx[submitter.NonceAttr].AsInt64(), int64(0)) - s.Require().Equal(tx[submitter.GasLimitAttr].AsInt64(), int64(0)) - s.Require().Equal(tx[submitter.ToAttr].AsString(), submitter.NullFieldAttribute) - s.Require().Equal(tx[submitter.ValueAttr].AsString(), "0") - s.Require().Equal(tx[submitter.DataAttr].AsString(), "") - - if rawTx.Type() == types.DynamicFeeTxType { - s.Require().Equal(tx[submitter.GasTipCapAttr].AsString(), "0") - s.Require().Equal(tx[submitter.GasFeeCapAttr].AsString(), "0") - } - if rawTx.Type() == types.LegacyTxType { - s.Require().Equal(tx[submitter.GasPriceAttr].AsString(), "0") - } -} - -func (s *SubmitterSuite) TestTxToAttributesLegacyTX() { - mockTX := mocks.GetMockTxes(s.GetTestContext(), s.T(), 1, types.LegacyTxType)[0] - mapAttr := makeAttrMap(mockTX, uuid.New().String()) - - s.Require().Equal(mapAttr[submitter.HashAttr].AsString(), mockTX.Hash().String()) - s.Require().Equal(mapAttr[submitter.NonceAttr].AsInt64(), int64(mockTX.Nonce())) - s.Require().Equal(mapAttr[submitter.GasLimitAttr].AsInt64(), int64(mockTX.Gas())) - s.Require().Equal(mapAttr[submitter.ToAttr].AsString(), mockTX.To().String()) - s.Require().Equal(mapAttr[submitter.ValueAttr].AsString(), mockTX.Value().String()) - s.Require().Equal(mapAttr[submitter.DataAttr].AsString(), "") - - s.Require().Equal(mapAttr[submitter.GasPriceAttr].AsString(), mockTX.GasPrice().String()) - _, hasFeeCap := mapAttr[submitter.GasFeeCapAttr] - _, hasTipCap := mapAttr[submitter.GasTipCapAttr] - s.Require().False(hasFeeCap) - s.Require().False(hasTipCap) - s.Require().NotNil(mapAttr[submitter.FromAttr]) -} - -func (s *SubmitterSuite) TestTxToAttributesDynamicTX() { - mockTX := mocks.GetMockTxes(s.GetTestContext(), s.T(), 1, types.DynamicFeeTxType)[0] - mapAttr := makeAttrMap(mockTX, uuid.New().String()) - - s.Require().Equal(mapAttr[submitter.HashAttr].AsString(), mockTX.Hash().String()) - s.Require().Equal(mapAttr[submitter.NonceAttr].AsInt64(), int64(mockTX.Nonce())) - s.Require().Equal(mapAttr[submitter.GasLimitAttr].AsInt64(), int64(mockTX.Gas())) - s.Require().Equal(mapAttr[submitter.ToAttr].AsString(), mockTX.To().String()) - s.Require().Equal(mapAttr[submitter.ValueAttr].AsString(), mockTX.Value().String()) - s.Require().Equal(mapAttr[submitter.DataAttr].AsString(), "") - - s.Require().Equal(mapAttr[submitter.GasFeeCapAttr].AsString(), mockTX.GasFeeCap().String()) - s.Require().Equal(mapAttr[submitter.GasTipCapAttr].AsString(), mockTX.GasTipCap().String()) - _, hasGasPrice := mapAttr[submitter.GasPriceAttr] - s.Require().False(hasGasPrice) - s.Require().NotNil(mapAttr[submitter.FromAttr]) -} - func (s *SubmitterSuite) TestSortTxes() { expected := make(map[uint64][]*types.Transaction) var allTxes []db.TX @@ -289,15 +198,6 @@ func (s *SubmitterSuite) TestGroupTxesByNonce() { } } -func makeAttrMap(tx *types.Transaction, UUID string) map[string]attribute.Value { - mapAttr := make(map[string]attribute.Value) - attr := submitter.TxToAttributes(tx, UUID) - for _, a := range attr { - mapAttr[string(a.Key)] = a.Value - } - return mapAttr -} - // Test for the outersection function. func TestOutersection(t *testing.T) { set := []*big.Int{ diff --git a/ethergo/util/attributes.go b/ethergo/util/attributes.go new file mode 100644 index 0000000000..38b1ff2d0f --- /dev/null +++ b/ethergo/util/attributes.go @@ -0,0 +1,80 @@ +package util + +import ( + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "go.opentelemetry.io/otel/attribute" + "math/big" +) + +const nullFieldAttribute = "null" + +const ( + hashAttr = "tx.Hash" + fromAttr = "tx.From" + toAttr = "tx.To" + dataAttr = "tx.Data" + valueAttr = "tx.Value" + nonceAttr = "tx.Nonce" + gasLimitAttr = "tx.GasLimit" + chainIDAttr = "tx.ChainID" + gasPriceAttr = "tx.GasPrice" + gasFeeCapAttr = "tx.GasFeeCap" + gasTipCapAttr = "tx.GasTipCap" +) + +// TxToAttributes converts a transaction to a slice of attribute.KeyValue. +func TxToAttributes(transaction *types.Transaction) []attribute.KeyValue { + var from string + call, err := TxToCall(transaction) + if err != nil { + from = fmt.Sprintf("could not be detected: %v", err) + } else { + from = call.From.Hex() + } + var attributes = []attribute.KeyValue{ + attribute.String(hashAttr, transaction.Hash().Hex()), + attribute.String(fromAttr, from), + attribute.String(toAttr, addressPtrToString(transaction.To())), + attribute.String(dataAttr, fmt.Sprintf("%x", transaction.Data())), + attribute.String(valueAttr, BigPtrToString(transaction.Value())), + // TODO: this could be downcast to int64, but it's unclear how we should handle overflows. + // since this is only for tracing, we can probably ignore it for now. + // nolint: gosec + attribute.Int64(nonceAttr, int64(transaction.Nonce())), + // nolint: gosec + attribute.Int64(gasLimitAttr, int64(transaction.Gas())), + attribute.String(chainIDAttr, BigPtrToString(transaction.ChainId())), + } + + if transaction.Type() == types.LegacyTxType && transaction.GasPrice() != nil { + attributes = append(attributes, attribute.String(gasPriceAttr, BigPtrToString(transaction.GasPrice()))) + } + + if transaction.Type() == types.DynamicFeeTxType && transaction.GasFeeCap() != nil { + attributes = append(attributes, attribute.String(gasFeeCapAttr, BigPtrToString(transaction.GasFeeCap()))) + } + + if transaction.Type() == types.DynamicFeeTxType && transaction.GasTipCap() != nil { + attributes = append(attributes, attribute.String(gasTipCapAttr, BigPtrToString(transaction.GasTipCap()))) + } + + return attributes +} + +func addressPtrToString(address *common.Address) string { + if address == nil { + return nullFieldAttribute + } + return address.Hex() +} + +// BigPtrToString converts a big.Int pointer to a string. +// TODO: move to core. +func BigPtrToString(num *big.Int) string { + if num == nil { + return nullFieldAttribute + } + return num.String() +} diff --git a/ethergo/util/attributes_test.go b/ethergo/util/attributes_test.go new file mode 100644 index 0000000000..7c91b200d4 --- /dev/null +++ b/ethergo/util/attributes_test.go @@ -0,0 +1,115 @@ +package util_test + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" + "github.com/synapsecns/sanguine/core" + "github.com/synapsecns/sanguine/ethergo/mocks" + "github.com/synapsecns/sanguine/ethergo/util" + "go.opentelemetry.io/otel/attribute" + "math/big" + "testing" +) + +func TestAddressPtrToString(t *testing.T) { + // Test case 1: Address is nil + var address *common.Address + assert.Equal(t, util.AddressPtrToString(address), util.NullFieldAttribute) + + // Test case 2: Address is not nil + address = core.PtrTo[common.Address](common.HexToAddress("0x1234567890123456789012345678901234567890")) + assert.Equal(t, util.AddressPtrToString(address), "0x1234567890123456789012345678901234567890") +} + +func TestBigPtrToString(t *testing.T) { + // Test case: num is nil + var num *big.Int + expected := util.NullFieldAttribute + result := util.BigPtrToString(num) + if result != expected { + t.Errorf("BigPtrToString(nil) = %q; want %q", result, expected) + } + + // Test case: num is an integer + num = big.NewInt(123) + expected = "123" + result = util.BigPtrToString(num) + if result != expected { + t.Errorf("BigPtrToString(123) = %q; want %q", result, expected) + } +} + +func makeAttrMap(tx *types.Transaction) map[string]attribute.Value { + mapAttr := make(map[string]attribute.Value) + attr := util.TxToAttributes(tx) + for _, a := range attr { + mapAttr[string(a.Key)] = a.Value + } + return mapAttr +} + +func (u *UtilSuite) checkEmptyTx(rawTx *types.Transaction) { + tx := makeAttrMap(rawTx) + + u.Require().Equal(tx[util.HashAttr].AsString(), rawTx.Hash().Hex()) + u.Require().Equal(tx[util.NonceAttr].AsInt64(), int64(0)) + u.Require().Equal(tx[util.GasLimitAttr].AsInt64(), int64(0)) + u.Require().Equal(tx[util.ToAttr].AsString(), util.NullFieldAttribute) + u.Require().Equal(tx[util.ValueAttr].AsString(), "0") + u.Require().Equal(tx[util.DataAttr].AsString(), "") + + if rawTx.Type() == types.DynamicFeeTxType { + u.Require().Equal(tx[util.GasTipCapAttr].AsString(), "0") + u.Require().Equal(tx[util.GasFeeCapAttr].AsString(), "0") + } + if rawTx.Type() == types.LegacyTxType { + u.Require().Equal(tx[util.GasPriceAttr].AsString(), "0") + } +} + +func (u *UtilSuite) TestTxToAttributesNullFields() { + u.checkEmptyTx(types.NewTx(&types.DynamicFeeTx{})) + u.checkEmptyTx(types.NewTx(&types.LegacyTx{})) +} + +func (u *UtilSuite) TestTxToAttributesLegacyTX() { + mockTX := mocks.GetMockTxes(u.GetTestContext(), u.T(), 1, types.LegacyTxType)[0] + mapAttr := makeAttrMap(mockTX) + + u.Require().Equal(mapAttr[util.HashAttr].AsString(), mockTX.Hash().String()) + // nolint: gosec + u.Require().Equal(mapAttr[util.NonceAttr].AsInt64(), int64(mockTX.Nonce())) + // nolint: gosec + u.Require().Equal(mapAttr[util.GasLimitAttr].AsInt64(), int64(mockTX.Gas())) + u.Require().Equal(mapAttr[util.ToAttr].AsString(), mockTX.To().String()) + u.Require().Equal(mapAttr[util.ValueAttr].AsString(), mockTX.Value().String()) + u.Require().Equal(mapAttr[util.DataAttr].AsString(), "") + + u.Require().Equal(mapAttr[util.GasPriceAttr].AsString(), mockTX.GasPrice().String()) + _, hasFeeCap := mapAttr[util.GasFeeCapAttr] + _, hasTipCap := mapAttr[util.GasTipCapAttr] + u.Require().False(hasFeeCap) + u.Require().False(hasTipCap) + u.Require().NotNil(mapAttr[util.FromAttr]) +} + +func (u *UtilSuite) TestTxToAttributesDynamicTX() { + mockTX := mocks.GetMockTxes(u.GetTestContext(), u.T(), 1, types.DynamicFeeTxType)[0] + mapAttr := makeAttrMap(mockTX) + + u.Require().Equal(mapAttr[util.HashAttr].AsString(), mockTX.Hash().String()) + // nolint: gosec + u.Require().Equal(mapAttr[util.NonceAttr].AsInt64(), int64(mockTX.Nonce())) + // nolint: gosec + u.Require().Equal(mapAttr[util.GasLimitAttr].AsInt64(), int64(mockTX.Gas())) + u.Require().Equal(mapAttr[util.ToAttr].AsString(), mockTX.To().String()) + u.Require().Equal(mapAttr[util.ValueAttr].AsString(), mockTX.Value().String()) + u.Require().Equal(mapAttr[util.DataAttr].AsString(), "") + + u.Require().Equal(mapAttr[util.GasFeeCapAttr].AsString(), mockTX.GasFeeCap().String()) + u.Require().Equal(mapAttr[util.GasTipCapAttr].AsString(), mockTX.GasTipCap().String()) + _, hasGasPrice := mapAttr[util.GasPriceAttr] + u.Require().False(hasGasPrice) + u.Require().NotNil(mapAttr[util.FromAttr]) +} diff --git a/ethergo/util/export_test.go b/ethergo/util/export_test.go index cb7255c762..fc357be3ab 100644 --- a/ethergo/util/export_test.go +++ b/ethergo/util/export_test.go @@ -1,6 +1,9 @@ package util -import "math/big" +import ( + "github.com/ethereum/go-ethereum/common" + "math/big" +) func MakeOptions(options ...CopyOption) TestCopyOptions { return makeOptions(options...) @@ -37,3 +40,36 @@ func (c copyOptions) GasTipCap() *big.Int { func (c copyOptions) TxType() *uint8 { return c.txType } + +// NullFieldAttribute is a constant used to test the null field attribute. +// it exports the underlying constant for testing. +const NullFieldAttribute = nullFieldAttribute + +func AddressPtrToString(address *common.Address) string { + return addressPtrToString(address) +} + +const ( + // HashAttr exports hashAttr for testing. + HashAttr = hashAttr + // FromAttr exports fromAttr for testing. + FromAttr = fromAttr + // ToAttr exports toAttr for testing. + ToAttr = toAttr + // DataAttr exports dataAttr for testing. + DataAttr = dataAttr + // ValueAttr exports valueAttr for testing. + ValueAttr = valueAttr + // NonceAttr exports nonceAttr for testing. + NonceAttr = nonceAttr + // GasLimitAttr exports gasLimitAttr for testing. + GasLimitAttr = gasLimitAttr + // ChainIDAttr exports chainIDAttr for testing. + ChainIDAttr = chainIDAttr + // GasPriceAttr exports gasPriceAttr for testing. + GasPriceAttr = gasPriceAttr + // GasFeeCapAttr exports gasFeeCapAttr for testing. + GasFeeCapAttr = gasFeeCapAttr + // GasTipCapAttr exports gasTipCapAttr for testing. + GasTipCapAttr = gasTipCapAttr +) diff --git a/services/omnirpc/modules/README.md b/services/omnirpc/modules/README.md index 000b17df9b..1af2170760 100644 --- a/services/omnirpc/modules/README.md +++ b/services/omnirpc/modules/README.md @@ -1,3 +1,6 @@ # Modules Modules are implementations that can modify the inputs to or outputs of an rpc call. They are meant to deal w/ specific application level limitations or requirements. For example, a module could be used to add a custom header to all requests, or to modify the response of a call to a specific service. These do not neccesarily emulate the original functionality of omnirpc and are run through seperate commands. + + +Mixins are meant to add metadata to make debugging easier. diff --git a/services/omnirpc/modules/confirmedtofinalized/finalizedproxy.go b/services/omnirpc/modules/confirmedtofinalized/finalizedproxy.go index aca1e6faaa..ce974daa3f 100644 --- a/services/omnirpc/modules/confirmedtofinalized/finalizedproxy.go +++ b/services/omnirpc/modules/confirmedtofinalized/finalizedproxy.go @@ -18,6 +18,7 @@ import ( "github.com/synapsecns/sanguine/ethergo/parser/rpc" "github.com/synapsecns/sanguine/services/omnirpc/collection" omniHTTP "github.com/synapsecns/sanguine/services/omnirpc/http" + "github.com/synapsecns/sanguine/services/omnirpc/modules/mixins" "github.com/synapsecns/sanguine/services/omnirpc/swagger" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -189,10 +190,7 @@ func (r *finalizedProxyImpl) checkShouldRequest(parentCtx context.Context, req r metrics.EndSpanWithErr(span, err) }() - tx := new(types.Transaction) - - hex := common.FromHex(string(bytes.ReplaceAll(req.Params[0], []byte{'"'}, []byte{}))) - err = tx.UnmarshalBinary(hex) + tx, err := mixins.ReqToTX(req) if err != nil { return false } diff --git a/services/omnirpc/modules/mixins/doc.go b/services/omnirpc/modules/mixins/doc.go new file mode 100644 index 0000000000..57b49f7e73 --- /dev/null +++ b/services/omnirpc/modules/mixins/doc.go @@ -0,0 +1,2 @@ +// Package mixins provides a set of mixins for the omnirpc module. +package mixins diff --git a/services/omnirpc/modules/mixins/helpers.go b/services/omnirpc/modules/mixins/helpers.go new file mode 100644 index 0000000000..1e7d3b51b7 --- /dev/null +++ b/services/omnirpc/modules/mixins/helpers.go @@ -0,0 +1,22 @@ +package mixins + +import ( + "bytes" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/synapsecns/sanguine/ethergo/parser/rpc" +) + +// ReqToTX converts a request to a transaction. +func ReqToTX(req rpc.Request) (tx *types.Transaction, err error) { + tx = new(types.Transaction) + + hex := common.FromHex(string(bytes.ReplaceAll(req.Params[0], []byte{'"'}, []byte{}))) + err = tx.UnmarshalBinary(hex) + if err != nil { + return nil, fmt.Errorf("could not unmarshal transaction: %w", err) + } + + return tx, nil +} diff --git a/services/omnirpc/modules/mixins/txsubmit.go b/services/omnirpc/modules/mixins/txsubmit.go new file mode 100644 index 0000000000..0d5d84fa26 --- /dev/null +++ b/services/omnirpc/modules/mixins/txsubmit.go @@ -0,0 +1,34 @@ +package mixins + +import ( + "context" + "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/ethergo/client" + "github.com/synapsecns/sanguine/ethergo/parser/rpc" + "github.com/synapsecns/sanguine/ethergo/util" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +// TxSubmitMixin is a mixin for tracking submitted transactions. +// it can be used to index additional data in otel regarding tx submission status. +func TxSubmitMixin(parentCtx context.Context, handler metrics.Handler, r rpc.Request) { + if client.RPCMethod(r.Method) != client.SendRawTransactionMethod { + return + } + + ctx, span := handler.Tracer().Start(parentCtx, "txsubmit", trace.WithAttributes(attribute.Int("txsubmit", r.ID))) + + var err error + defer func() { + metrics.EndSpanWithErr(span, err) + }() + + tx, err := ReqToTX(r) + if err != nil { + handler.ExperimentalLogger().Warnf(ctx, "could not convert request to transaction: %v", err) + return + } + + span.SetAttributes(util.TxToAttributes(tx)...) +} diff --git a/services/omnirpc/modules/receiptsbackup/receiptsbackup.go b/services/omnirpc/modules/receiptsbackup/receiptsbackup.go index 5cd02c1cac..d10b999ca2 100644 --- a/services/omnirpc/modules/receiptsbackup/receiptsbackup.go +++ b/services/omnirpc/modules/receiptsbackup/receiptsbackup.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/synapsecns/sanguine/services/omnirpc/modules/mixins" "io" "net/http" "time" @@ -136,6 +137,8 @@ func (r *receiptsProxyImpl) ProxyRequest(c *gin.Context) (err error) { } func (r *receiptsProxyImpl) processRequest(ctx context.Context, rpcRequest rpc.Request, requestID []byte) (resp omniHTTP.Response, err error) { + mixins.TxSubmitMixin(ctx, r.handler, rpcRequest) + req := r.client.NewRequest() body, err := json.Marshal(rpcRequest)