Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache submitter gas block #1465

Merged
merged 8 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion ethergo/submitter/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package submitter

import (
"context"
"math/big"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -11,7 +13,6 @@ import (
"github.com/synapsecns/sanguine/ethergo/submitter/config"
"github.com/synapsecns/sanguine/ethergo/submitter/db"
"go.opentelemetry.io/otel/attribute"
"math/big"
)

// CopyTransactOpts exports copyTransactOpts for testing.
Expand Down Expand Up @@ -85,6 +86,8 @@ type TestTransactionSubmitter interface {
// SetGasPrice exports setGasPrice for testing.
SetGasPrice(ctx context.Context, client client.EVM,
transactor *bind.TransactOpts, bigChainID *big.Int, prevTx *types.Transaction) (err error)
// GetGasBlock exports getGasBlock for testing.
GetGasBlock(ctx context.Context, client client.EVM, chainID int) (gasBlock *types.Header, err error)
// GetNonce exports getNonce for testing.
GetNonce(parentCtx context.Context, chainID *big.Int, address common.Address) (_ uint64, err error)
// CheckAndSetConfirmation exports checkAndSetConfirmation for testing.
Expand All @@ -97,6 +100,11 @@ func (t *txSubmitterImpl) SetGasPrice(ctx context.Context, client client.EVM,
return t.setGasPrice(ctx, client, transactor, bigChainID, prevTx)
}

// GetGasBlock exports getGasBlock for testing.
func (t *txSubmitterImpl) GetGasBlock(ctx context.Context, client client.EVM, chainID int) (gasBlock *types.Header, err error) {
return t.getGasBlock(ctx, client, chainID)
}

// GetNonce exports getNonce for testing.
func (t *txSubmitterImpl) GetNonce(parentCtx context.Context, chainID *big.Int, address common.Address) (_ uint64, err error) {
return t.getNonce(parentCtx, chainID, address)
Expand Down
53 changes: 35 additions & 18 deletions ethergo/submitter/submitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
"context"
"errors"
"fmt"
"math"
"math/big"
"reflect"
"runtime"
"sync"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -21,12 +28,6 @@
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"math"
"math/big"
"reflect"
"runtime"
"sync"
"time"
)

var logger = log.Logger("ethergo-submitter")
Expand Down Expand Up @@ -63,6 +64,8 @@
// to prevent memory leaks, this has a buffer of 1.
// callers adding to this channel should not block.
retryNow chan bool
// gasBlockCache is used to cache the gas block for a given chain.
gasBlockCache map[int]*types.Header
dwasse marked this conversation as resolved.
Show resolved Hide resolved
// config is the config for the transaction submitter.
config config.IConfig
}
Expand All @@ -77,14 +80,15 @@
// NewTransactionSubmitter creates a new transaction submitter.
func NewTransactionSubmitter(metrics metrics.Handler, signer signer.Signer, fetcher ClientFetcher, db db.Service, config config.IConfig) TransactionSubmitter {
return &txSubmitterImpl{
db: db,
config: config,
metrics: metrics,
signer: signer,
fetcher: fetcher,
nonceMux: mapmutex.NewStringerMapMutex(),
statusMux: mapmutex.NewStringMapMutex(),
retryNow: make(chan bool, 1),
db: db,
config: config,
metrics: metrics,
signer: signer,
fetcher: fetcher,
nonceMux: mapmutex.NewStringerMapMutex(),
statusMux: mapmutex.NewStringMapMutex(),
retryNow: make(chan bool, 1),
gasBlockCache: map[int]*types.Header{},
}
}

Expand Down Expand Up @@ -370,10 +374,10 @@

//nolint: nestif
if prevTx != nil {
// TODO: cache
gasBlock, err := t.getGasBlock(ctx, client)
gasBlock, err := t.getGasBlock(ctx, client, chainID)

Check warning on line 377 in ethergo/submitter/submitter.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L377

Added line #L377 was not covered by tests
if err != nil {
span.AddEvent("could not get gas block", trace.WithAttributes(attribute.String("error", err.Error())))
return err

Check warning on line 380 in ethergo/submitter/submitter.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L380

Added line #L380 was not covered by tests
}

// if the prev tx was greater than this one, we should bump the gas price from that point
Expand All @@ -392,7 +396,7 @@
}

// getGasBlock gets the gas block for the given chain.
func (t *txSubmitterImpl) getGasBlock(ctx context.Context, chainClient client.EVM) (gasBlock *types.Header, err error) {
func (t *txSubmitterImpl) getGasBlock(ctx context.Context, chainClient client.EVM, chainID int) (gasBlock *types.Header, err error) {
ctx, span := t.metrics.Tracer().Start(ctx, "submitter.getGasBlock")
defer func() {
metrics.EndSpanWithErr(span, err)
Expand All @@ -407,10 +411,23 @@
return nil
}, retry.WithMin(time.Millisecond*50), retry.WithMax(time.Second*3), retry.WithMaxAttempts(4))

// if we can't get the current gas block, attempt to load it from the cache
if err != nil {
return nil, fmt.Errorf("could not get gas block: %w", err)
var ok bool
gasBlock, ok = t.gasBlockCache[chainID]
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)),
))
} else {
return nil, fmt.Errorf("could not get gas block: %w", err)
}
}

// cache the latest gas block
t.gasBlockCache[chainID] = gasBlock

return gasBlock, nil
}

Expand Down
36 changes: 35 additions & 1 deletion ethergo/submitter/submitter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package submitter_test

import (
"fmt"
"math/big"

"github.com/brianvoe/gofakeit/v6"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -20,7 +22,6 @@ import (
dbMocks "github.com/synapsecns/sanguine/ethergo/submitter/db/mocks"
submitterMocks "github.com/synapsecns/sanguine/ethergo/submitter/mocks"
"github.com/synapsecns/sanguine/ethergo/util"
"math/big"
)

func (s *SubmitterSuite) TestSetGasPrice() {
Expand Down Expand Up @@ -72,6 +73,39 @@ func (s *SubmitterSuite) TestSetGasPrice() {
// 5. Test with bump and max (TODO)
}

func (s *SubmitterSuite) TestGetGasBlock() {
wall, err := wallet.FromRandom()
s.Require().NoError(err)

signer := localsigner.NewSigner(wall.PrivateKey())

chainID := s.testBackends[0].GetBigChainID()
client := new(clientMocks.EVM)

cfg := &config.Config{}
ts := submitter.NewTestTransactionSubmitter(s.metrics, signer, s, s.store, cfg)
currentHeader := &types.Header{Number: big.NewInt(1)}

// 1. Test with failed HeaderByNumber RPC call; Error is expected.
mockErrMsg := "mock error"
client.On(testsuite.GetFunctionName(client.HeaderByNumber), mock.Anything, mock.Anything).Times(5).Return(nil, fmt.Errorf(mockErrMsg))
gasBlock, err := ts.GetGasBlock(s.GetTestContext(), client, int(chainID.Int64()))
s.Nil(gasBlock)
s.NotNil(err)

// 2. Test with successful HeaderByNumber RPC call.
client.On(testsuite.GetFunctionName(client.HeaderByNumber), mock.Anything, mock.Anything).Once().Return(currentHeader, nil)
gasBlock, err = ts.GetGasBlock(s.GetTestContext(), client, int(chainID.Int64()))
s.Require().NoError(err)
s.Equal(gasBlock.Number.String(), currentHeader.Number.String())

// 3. Test with failed HeaderByNumber RPC call; the cached value should be used.
client.On(testsuite.GetFunctionName(client.HeaderByNumber), mock.Anything, mock.Anything).Times(5).Return(nil, fmt.Errorf(mockErrMsg))
gasBlock, err = ts.GetGasBlock(s.GetTestContext(), client, int(chainID.Int64()))
s.Require().NoError(err)
s.Equal(gasBlock.Number.String(), currentHeader.Number.String())
}

func (s *SubmitterSuite) TestGetNonce() {
chainID := s.testBackends[0].GetBigChainID()

Expand Down
Loading