Skip to content

Commit

Permalink
txmgr: add rpc api getters/setters (#10897)
Browse files Browse the repository at this point in the history
* Add txmgr rpc api

* Update mock TxManager

* Use parameterized tests to remove redundant code

* Add txmgr.cfgLock to protect values configurable at runtime

* txmgr: use generic API() method on interface to allow custom rpc apis

* txmgr: re-generate mocks

* txmgr: use atomics for Config vals that can be modified via rpc

* txmgr: use pointer for SimpleTxManager.cfg

* txmgr: remove extraneous code

* txmgr: cleanup ctx input arg in rpc methods
  • Loading branch information
bitwiseguy authored Aug 14, 2024
1 parent 376b11c commit 549b52e
Show file tree
Hide file tree
Showing 12 changed files with 338 additions and 87 deletions.
1 change: 1 addition & 0 deletions op-batcher/batcher/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ func (bs *BatcherService) initRPCServer(cfg *CLIConfig) error {
if cfg.RPC.EnableAdmin {
adminAPI := rpc.NewAdminAPI(bs.driver, bs.Metrics, bs.Log)
server.AddAPI(rpc.GetAdminAPI(adminAPI))
server.AddAPI(bs.TxManager.API())
bs.Log.Info("Admin RPC enabled")
}
bs.Log.Info("Starting JSON-RPC server")
Expand Down
5 changes: 5 additions & 0 deletions op-challenger/sender/sender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
"golang.org/x/exp/maps"
)
Expand Down Expand Up @@ -170,5 +171,9 @@ func (s *stubTxMgr) BlockNumber(_ context.Context) (uint64, error) {
panic("unsupported")
}

func (s *stubTxMgr) API() rpc.API {
panic("unimplemented")
}

func (s *stubTxMgr) Close() {
}
5 changes: 5 additions & 0 deletions op-e2e/actions/l2_proposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"

"github.com/ethereum-optimism/optimism/op-e2e/bindings"
Expand Down Expand Up @@ -74,6 +75,10 @@ func (f fakeTxMgr) IsClosed() bool {
return false
}

func (f fakeTxMgr) API() rpc.API {
panic("unimplemented")
}

func NewL2Proposer(t Testing, log log.Logger, cfg *ProposerCfg, l1 *ethclient.Client, rollupCl *sources.RollupClient) *L2Proposer {
proposerConfig := proposer.ProposerConfig{
PollInterval: time.Second,
Expand Down
1 change: 1 addition & 0 deletions op-proposer/proposer/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ func (ps *ProposerService) initRPCServer(cfg *CLIConfig) error {
if cfg.RPCConfig.EnableAdmin {
adminAPI := rpc.NewAdminAPI(ps.driver, ps.Metrics, ps.Log)
server.AddAPI(rpc.GetAdminAPI(adminAPI))
server.AddAPI(ps.TxManager.API())
ps.Log.Info("Admin RPC enabled")
}
ps.Log.Info("Starting JSON-RPC server")
Expand Down
63 changes: 38 additions & 25 deletions op-service/txmgr/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"math/big"
"sync/atomic"
"time"

opservice "github.com/ethereum-optimism/optimism/op-service"
Expand All @@ -14,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/urfave/cli/v2"
)

Expand Down Expand Up @@ -94,6 +96,9 @@ var (
TxNotInMempoolTimeout: 1 * time.Minute,
ReceiptQueryInterval: 12 * time.Second,
}

// geth enforces a 1 gwei minimum for blob tx fee
defaultMinBlobTxFee = big.NewInt(params.GWei)
)

func CLIFlags(envPrefix string) []cli.Flag {
Expand Down Expand Up @@ -286,23 +291,23 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig {
}
}

func NewConfig(cfg CLIConfig, l log.Logger) (Config, error) {
func NewConfig(cfg CLIConfig, l log.Logger) (*Config, error) {
if err := cfg.Check(); err != nil {
return Config{}, fmt.Errorf("invalid config: %w", err)
return nil, fmt.Errorf("invalid config: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), cfg.NetworkTimeout)
defer cancel()
l1, err := ethclient.DialContext(ctx, cfg.L1RPCURL)
if err != nil {
return Config{}, fmt.Errorf("could not dial eth client: %w", err)
return nil, fmt.Errorf("could not dial eth client: %w", err)
}

ctx, cancel = context.WithTimeout(context.Background(), cfg.NetworkTimeout)
defer cancel()
chainID, err := l1.ChainID(ctx)
if err != nil {
return Config{}, fmt.Errorf("could not dial fetch L1 chain ID: %w", err)
return nil, fmt.Errorf("could not dial fetch L1 chain ID: %w", err)
}

// Allow backwards compatible ways of specifying the HD path
Expand All @@ -315,31 +320,26 @@ func NewConfig(cfg CLIConfig, l log.Logger) (Config, error) {

signerFactory, from, err := opcrypto.SignerFactoryFromConfig(l, cfg.PrivateKey, cfg.Mnemonic, hdPath, cfg.SignerCLIConfig)
if err != nil {
return Config{}, fmt.Errorf("could not init signer: %w", err)
return nil, fmt.Errorf("could not init signer: %w", err)
}

feeLimitThreshold, err := eth.GweiToWei(cfg.FeeLimitThresholdGwei)
if err != nil {
return Config{}, fmt.Errorf("invalid fee limit threshold: %w", err)
return nil, fmt.Errorf("invalid fee limit threshold: %w", err)
}

minBaseFee, err := eth.GweiToWei(cfg.MinBaseFeeGwei)
if err != nil {
return Config{}, fmt.Errorf("invalid min base fee: %w", err)
return nil, fmt.Errorf("invalid min base fee: %w", err)
}

minTipCap, err := eth.GweiToWei(cfg.MinTipCapGwei)
if err != nil {
return Config{}, fmt.Errorf("invalid min tip cap: %w", err)
return nil, fmt.Errorf("invalid min tip cap: %w", err)
}

return Config{
res := Config{
Backend: l1,
ResubmissionTimeout: cfg.ResubmissionTimeout,
FeeLimitMultiplier: cfg.FeeLimitMultiplier,
FeeLimitThreshold: feeLimitThreshold,
MinBaseFee: minBaseFee,
MinTipCap: minTipCap,
ChainID: chainID,
TxSendTimeout: cfg.TxSendTimeout,
TxNotInMempoolTimeout: cfg.TxNotInMempoolTimeout,
Expand All @@ -349,7 +349,16 @@ func NewConfig(cfg CLIConfig, l log.Logger) (Config, error) {
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount,
Signer: signerFactory(chainID),
From: from,
}, nil
}

res.ResubmissionTimeout.Store(int64(cfg.ResubmissionTimeout))
res.FeeLimitThreshold.Store(feeLimitThreshold)
res.FeeLimitMultiplier.Store(cfg.FeeLimitMultiplier)
res.MinBaseFee.Store(minBaseFee)
res.MinTipCap.Store(minTipCap)
res.MinBlobTxFee.Store(defaultMinBlobTxFee)

return &res, nil
}

// Config houses parameters for altering the behavior of a SimpleTxManager.
Expand All @@ -359,21 +368,23 @@ type Config struct {
// published transaction has been mined, the new tx with a bumped gas
// price will be published. Only one publication at MaxGasPrice will be
// attempted.
ResubmissionTimeout time.Duration
ResubmissionTimeout atomic.Int64

// The multiplier applied to fee suggestions to put a hard limit on fee increases.
FeeLimitMultiplier uint64
FeeLimitMultiplier atomic.Uint64

// Minimum threshold (in Wei) at which the FeeLimitMultiplier takes effect.
// On low-fee networks, like test networks, this allows for arbitrary fee bumps
// below this threshold.
FeeLimitThreshold *big.Int
FeeLimitThreshold atomic.Pointer[big.Int]

// Minimum base fee (in Wei) to assume when determining tx fees.
MinBaseFee *big.Int
MinBaseFee atomic.Pointer[big.Int]

// Minimum tip cap (in Wei) to enforce when determining tx fees.
MinTipCap *big.Int
MinTipCap atomic.Pointer[big.Int]

MinBlobTxFee atomic.Pointer[big.Int]

// ChainID is the chain ID of the L1 chain.
ChainID *big.Int
Expand Down Expand Up @@ -409,7 +420,7 @@ type Config struct {
From common.Address
}

func (m Config) Check() error {
func (m *Config) Check() error {
if m.Backend == nil {
return errors.New("must provide the Backend")
}
Expand All @@ -419,14 +430,16 @@ func (m Config) Check() error {
if m.NetworkTimeout == 0 {
return errors.New("must provide NetworkTimeout")
}
if m.FeeLimitMultiplier == 0 {
if m.FeeLimitMultiplier.Load() == 0 {
return errors.New("must provide FeeLimitMultiplier")
}
if m.MinBaseFee != nil && m.MinTipCap != nil && m.MinBaseFee.Cmp(m.MinTipCap) == -1 {
minBaseFee := m.MinBaseFee.Load()
minTipCap := m.MinTipCap.Load()
if minBaseFee != nil && minTipCap != nil && minBaseFee.Cmp(minTipCap) == -1 {
return fmt.Errorf("minBaseFee smaller than minTipCap, have %v < %v",
m.MinBaseFee, m.MinTipCap)
minBaseFee, minTipCap)
}
if m.ResubmissionTimeout == 0 {
if m.ResubmissionTimeout.Load() == 0 {
return errors.New("must provide ResubmissionTimeout")
}
if m.ReceiptQueryInterval == 0 {
Expand Down
16 changes: 16 additions & 0 deletions op-service/txmgr/mocks/TxManager.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions op-service/txmgr/queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ func TestQueue_Send(t *testing.T) {
t.Parallel()

conf := configWithNumConfs(1)
conf.ReceiptQueryInterval = 1 * time.Second // simulate a network send
conf.ResubmissionTimeout = 2 * time.Second // resubmit to detect errors
conf.ReceiptQueryInterval = 1 * time.Second // simulate a network send
conf.ResubmissionTimeout.Store(int64(2 * time.Second)) // resubmit to detect errors
conf.SafeAbortNonceTooLowCount = 1
backend := newMockBackendWithNonce(newGasPricer(3))
mgr := &SimpleTxManager{
Expand Down
54 changes: 54 additions & 0 deletions op-service/txmgr/rpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package txmgr

import (
"context"
"math/big"
"time"

"github.com/ethereum/go-ethereum/log"
)

type SimpleTxmgrAPI struct {
mgr *SimpleTxManager
l log.Logger
}

func (a *SimpleTxmgrAPI) GetMinBaseFee(_ context.Context) *big.Int {
return a.mgr.GetMinBaseFee()
}

func (a *SimpleTxmgrAPI) SetMinBaseFee(_ context.Context, val *big.Int) {
a.mgr.SetMinBaseFee(val)
}

func (a *SimpleTxmgrAPI) GetPriorityFee(_ context.Context) *big.Int {
return a.mgr.GetPriorityFee()
}

func (a *SimpleTxmgrAPI) SetPriorityFee(_ context.Context, val *big.Int) {
a.mgr.SetPriorityFee(val)
}

func (a *SimpleTxmgrAPI) GetMinBlobFee(_ context.Context) *big.Int {
return a.mgr.GetMinBlobFee()
}

func (a *SimpleTxmgrAPI) SetMinBlobFee(_ context.Context, val *big.Int) {
a.mgr.SetMinBlobFee(val)
}

func (a *SimpleTxmgrAPI) GetFeeThreshold(_ context.Context) *big.Int {
return a.mgr.GetFeeThreshold()
}

func (a *SimpleTxmgrAPI) SetFeeThreshold(_ context.Context, val *big.Int) {
a.mgr.SetFeeThreshold(val)
}

func (a *SimpleTxmgrAPI) GetBumpFeeRetryTime(_ context.Context) time.Duration {
return a.mgr.GetBumpFeeRetryTime()
}

func (a *SimpleTxmgrAPI) SetBumpFeeRetryTime(_ context.Context, val time.Duration) {
a.mgr.SetBumpFeeRetryTime(val)
}
64 changes: 64 additions & 0 deletions op-service/txmgr/rpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package txmgr

import (
"fmt"
"math/big"
"testing"

oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)

func TestTxmgrRPC(t *testing.T) {
minBaseFee := big.NewInt(1000)
priorityFee := big.NewInt(2000)
minBlobFee := big.NewInt(3000)
feeThreshold := big.NewInt(4000)

cfg := Config{}
cfg.MinBaseFee.Store(minBaseFee)
cfg.MinTipCap.Store(priorityFee)
cfg.MinBlobTxFee.Store(minBlobFee)
cfg.FeeLimitThreshold.Store(feeThreshold)

h := newTestHarnessWithConfig(t, &cfg)

appVersion := "test"
server := oprpc.NewServer(
"127.0.0.1",
0,
appVersion,
oprpc.WithAPIs([]rpc.API{
h.mgr.API(),
}),
)
require.NoError(t, server.Start())
defer func() {
_ = server.Stop()
}()

rpcClient, err := rpc.Dial(fmt.Sprintf("http://%s", server.Endpoint()))
require.NoError(t, err)

type tcase struct {
rpcMethod string
value *big.Int
}

cases := []tcase{
{"MinBaseFee", big.NewInt(1001)},
{"PriorityFee", big.NewInt(2001)},
{"MinBlobFee", big.NewInt(3001)},
{"FeeThreshold", big.NewInt(4001)},
}

for _, tc := range cases {
t.Run(tc.rpcMethod, func(t *testing.T) {
var res *big.Int
require.NoError(t, rpcClient.Call(&res, "txmgr_set"+tc.rpcMethod, tc.value))
require.NoError(t, rpcClient.Call(&res, "txmgr_get"+tc.rpcMethod))
require.Equal(t, tc.value, res)
})
}
}
2 changes: 1 addition & 1 deletion op-service/txmgr/test_txmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (m *TestTxManager) makeStuckTx(ctx context.Context, candidate TxCandidate)

var txMessage types.TxData
if sidecar != nil {
blobFeeCap := calcBlobFeeCap(blobBaseFee)
blobFeeCap := m.calcBlobFeeCap(blobBaseFee)
message := &types.BlobTx{
To: *candidate.To,
Data: candidate.TxData,
Expand Down
Loading

0 comments on commit 549b52e

Please sign in to comment.