infra/op-ufm/pkg/provider/roundtrip.go

313 lines
8.0 KiB
Go
Raw Permalink Normal View History

package provider
import (
"context"
2023-09-06 15:14:26 -07:00
"math/big"
2023-07-12 10:28:32 -07:00
"time"
2023-07-14 15:17:02 -07:00
"github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics"
iclients "github.com/ethereum-optimism/optimism/op-ufm/pkg/metrics/clients"
2023-08-31 11:55:32 -07:00
"github.com/ethereum/go-ethereum/core"
2023-07-14 15:17:02 -07:00
2023-07-12 09:49:37 -07:00
"github.com/ethereum-optimism/optimism/op-service/tls"
2023-07-12 10:28:32 -07:00
"github.com/ethereum/go-ethereum"
2023-07-12 09:49:37 -07:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/txpool"
2023-07-12 09:49:37 -07:00
"github.com/ethereum/go-ethereum/crypto"
"github.com/pkg/errors"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// RoundTrip send a new transaction to measure round trip latency
func (p *Provider) RoundTrip(ctx context.Context) {
2023-09-06 15:14:26 -07:00
log.Debug("RoundTrip",
2023-08-31 13:31:34 -07:00
"provider", p.name)
2023-07-12 09:49:37 -07:00
client, err := iclients.Dial(p.name, p.config.URL)
2023-07-12 09:49:37 -07:00
if err != nil {
2023-08-31 13:31:34 -07:00
log.Error("cant dial to provider",
"provider", p.name,
"url", p.config.URL,
"err", err)
return
2023-07-12 09:49:37 -07:00
}
2023-09-06 15:15:19 -07:00
p.txPool.ExclusiveSend.Lock()
defer p.txPool.ExclusiveSend.Unlock()
// lint:ignore SA4006 txHash is set and used within tx sending loop
txHash := common.Hash{}
attempt := 0
2023-09-06 15:12:13 -07:00
nonce := uint64(0)
2023-07-14 14:08:02 -07:00
// used for timeout
firstAttemptAt := time.Now()
// used for actual round trip time (disregard retry time)
2023-09-06 15:14:26 -07:00
var roundTripStartedAt time.Time
for {
2023-09-06 15:14:26 -07:00
2023-09-06 15:07:17 -07:00
// sleep until we get a clear to send
for {
coolDown := time.Duration(p.config.SendTransactionCoolDown) - time.Since(p.txPool.LastSend)
if coolDown > 0 {
time.Sleep(coolDown)
} else {
break
}
}
2023-12-12 16:23:39 -08:00
from, tx, err := p.createTx(ctx, client, nonce)
2023-09-06 15:12:13 -07:00
if err != nil {
2023-09-06 15:14:26 -07:00
log.Error("cant create tx",
2023-09-06 15:12:13 -07:00
"provider", p.name,
2023-09-06 15:14:26 -07:00
"nonce", nonce,
2023-09-06 15:12:13 -07:00
"err", err)
return
}
nonce = tx.Nonce()
2023-09-06 15:12:13 -07:00
2023-12-12 16:23:39 -08:00
signedTx, err := p.sign(ctx, from, tx)
if err != nil {
2023-08-31 13:31:34 -07:00
log.Error("cant sign tx",
"provider", p.name,
"tx", tx,
"err", err)
return
}
txHash = signedTx.Hash()
2023-07-14 14:08:02 -07:00
roundTripStartedAt = time.Now()
err = client.SendTransaction(ctx, signedTx)
if err != nil {
2023-08-31 11:55:32 -07:00
if err.Error() == txpool.ErrAlreadyKnown.Error() ||
err.Error() == txpool.ErrReplaceUnderpriced.Error() ||
err.Error() == core.ErrNonceTooLow.Error() {
2023-09-06 15:12:13 -07:00
2023-09-07 13:25:50 -07:00
log.Warn("cant send transaction (retryable)",
"provider", p.name,
"err", err,
"nonce", nonce)
2023-07-14 14:08:02 -07:00
if time.Since(firstAttemptAt) >= time.Duration(p.config.SendTransactionRetryTimeout) {
2023-08-31 13:31:34 -07:00
log.Error("send transaction timed out (known already)",
"provider", p.name,
"hash", txHash.Hex(),
2023-09-06 15:12:13 -07:00
"nonce", nonce,
2023-08-31 13:31:34 -07:00
"elapsed", time.Since(firstAttemptAt),
2023-09-06 15:12:13 -07:00
"attempt", attempt)
2023-09-07 13:25:50 -07:00
metrics.RecordErrorDetails(p.name, "send.timeout", err)
return
}
2023-09-06 15:14:26 -07:00
2023-08-31 13:31:34 -07:00
log.Warn("tx already known, incrementing nonce and trying again",
"provider", p.name,
"nonce", nonce)
time.Sleep(time.Duration(p.config.SendTransactionRetryInterval))
2023-08-31 11:55:32 -07:00
2023-09-06 15:14:26 -07:00
nonce++
attempt++
if attempt%10 == 0 {
2023-08-31 13:31:34 -07:00
log.Debug("retrying send transaction...",
"provider", p.name,
"attempt", attempt,
"nonce", nonce,
"elapsed", time.Since(firstAttemptAt))
}
} else {
2023-08-31 13:31:34 -07:00
log.Error("cant send transaction",
"provider", p.name,
2023-09-06 15:12:13 -07:00
"nonce", nonce,
2023-08-31 13:31:34 -07:00
"err", err)
2023-07-14 14:36:58 -07:00
metrics.RecordErrorDetails(p.name, "ethclient.SendTransaction", err)
return
}
} else {
break
}
2023-07-12 09:49:37 -07:00
}
2023-08-31 13:31:34 -07:00
log.Info("transaction sent",
"provider", p.name,
"hash", txHash.Hex(),
"nonce", nonce)
2023-07-12 10:28:32 -07:00
2023-07-12 12:20:23 -07:00
// add to pool
2023-07-12 10:28:32 -07:00
sentAt := time.Now()
2023-07-12 12:20:23 -07:00
p.txPool.M.Lock()
p.txPool.Transactions[txHash.Hex()] = &TransactionState{
Hash: txHash,
2023-07-18 10:21:19 -07:00
ProviderSource: p.name,
SentAt: sentAt,
SeenBy: make(map[string]time.Time),
2023-07-12 12:20:23 -07:00
}
2023-09-06 15:12:13 -07:00
p.txPool.LastSend = sentAt
2023-07-12 12:20:23 -07:00
p.txPool.M.Unlock()
2023-07-12 10:28:32 -07:00
var receipt *types.Receipt
attempt = 0
2023-07-12 10:28:32 -07:00
for receipt == nil {
if time.Since(sentAt) >= time.Duration(p.config.ReceiptRetrievalTimeout) {
2023-08-31 13:31:34 -07:00
log.Error("receipt retrieval timed out",
"provider", p.name,
"hash", txHash,
2023-09-06 15:12:13 -07:00
"nonce", nonce,
2023-08-31 13:31:34 -07:00
"elapsed", time.Since(sentAt))
2023-09-07 13:25:50 -07:00
metrics.RecordErrorDetails(p.name, "receipt.timeout", err)
return
2023-07-12 10:28:32 -07:00
}
time.Sleep(time.Duration(p.config.ReceiptRetrievalInterval))
if attempt%10 == 0 {
2023-08-31 13:31:34 -07:00
log.Debug("checking for receipt...",
"provider", p.name,
2023-09-06 15:12:13 -07:00
"hash", txHash,
"nonce", nonce,
2023-08-31 13:31:34 -07:00
"attempt", attempt,
"elapsed", time.Since(sentAt))
2023-07-12 10:28:32 -07:00
}
receipt, err = client.TransactionReceipt(ctx, txHash)
2023-07-12 10:28:32 -07:00
if err != nil && !errors.Is(err, ethereum.NotFound) {
2023-08-31 13:31:34 -07:00
log.Error("cant get receipt for transaction",
"provider", p.name,
"hash", txHash.Hex(),
2023-09-06 15:12:13 -07:00
"nonce", nonce,
2023-08-31 13:31:34 -07:00
"err", err)
return
2023-07-12 10:28:32 -07:00
}
attempt++
}
2023-07-14 14:08:02 -07:00
roundTripLatency := time.Since(roundTripStartedAt)
metrics.RecordRoundTripLatency(p.name, roundTripLatency)
metrics.RecordGasUsed(p.name, receipt.GasUsed)
2023-07-12 10:28:32 -07:00
2023-08-31 13:31:34 -07:00
log.Info("got transaction receipt",
"hash", txHash.Hex(),
2023-09-06 15:12:13 -07:00
"nonce", nonce,
2023-07-14 14:08:02 -07:00
"roundTripLatency", roundTripLatency,
"provider", p.name,
2023-07-12 10:28:32 -07:00
"blockNumber", receipt.BlockNumber,
"blockHash", receipt.BlockHash,
"gasUsed", receipt.GasUsed)
2023-07-12 09:49:37 -07:00
}
2023-12-12 16:23:39 -08:00
func (p *Provider) createTx(ctx context.Context, client *iclients.InstrumentedEthClient, nonce uint64) (*common.Address, *types.Transaction, error) {
2023-09-06 15:14:26 -07:00
var err error
if nonce == 0 {
nonce, err = client.PendingNonceAt(ctx, p.walletConfig.Address)
if err != nil {
2023-12-12 16:43:42 -08:00
log.Error("cant get nonce",
2023-09-06 15:14:26 -07:00
"provider", p.name,
"nonce", nonce,
"err", err)
2023-12-12 16:23:39 -08:00
return nil, nil, err
2023-09-06 15:14:26 -07:00
}
}
gasTipCap, err := client.SuggestGasTipCap(ctx)
if err != nil {
log.Error("cant get gas tip cap",
"provider", p.name,
"err", err)
2023-12-12 16:23:39 -08:00
return nil, nil, err
2023-09-06 15:14:26 -07:00
}
2023-12-12 17:40:45 -08:00
// adjust gas tip cap by 110%
const GasTipCapAdjustmentMultiplier = 110
const GasTipCapAdjustmentDivisor = 100
gasTipCap = new(big.Int).Mul(gasTipCap, big.NewInt(GasTipCapAdjustmentMultiplier))
gasTipCap = new(big.Int).Div(gasTipCap, big.NewInt(GasTipCapAdjustmentDivisor))
2023-09-06 15:14:26 -07:00
head, err := client.HeaderByNumber(ctx, nil)
if err != nil {
log.Error("cant get base fee from head",
"provider", p.name,
"err", err)
2023-12-12 16:23:39 -08:00
return nil, nil, err
2023-09-06 15:14:26 -07:00
}
baseFee := head.BaseFee
gasFeeCap := new(big.Int).Add(
gasTipCap,
new(big.Int).Mul(baseFee, big.NewInt(2)))
addr := common.HexToAddress(p.walletConfig.Address)
2023-07-12 09:49:37 -07:00
var data []byte
2023-09-06 15:14:26 -07:00
dynamicTx := &types.DynamicFeeTx{
2023-07-12 09:49:37 -07:00
ChainID: &p.walletConfig.ChainID,
Nonce: nonce,
2023-09-06 15:14:26 -07:00
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
To: &addr,
2023-07-12 09:49:37 -07:00
Value: &p.walletConfig.TxValue,
Data: data,
2023-09-06 15:14:26 -07:00
}
gas, err := client.EstimateGas(ctx, ethereum.CallMsg{
From: addr,
To: &addr,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Data: dynamicTx.Data,
Value: dynamicTx.Value,
2023-07-12 09:49:37 -07:00
})
2023-09-06 15:14:26 -07:00
if err != nil {
log.Error("cant estimate gas",
"provider", p.name,
"err", err)
2023-12-12 16:23:39 -08:00
return nil, nil, err
2023-09-06 15:14:26 -07:00
}
dynamicTx.Gas = gas
tx := types.NewTx(dynamicTx)
2023-09-07 13:25:50 -07:00
log.Info("tx created",
2023-09-06 15:14:26 -07:00
"provider", p.name,
2023-09-07 13:25:50 -07:00
"from", addr,
"to", dynamicTx.To,
"nonce", dynamicTx.Nonce,
"value", dynamicTx.Value,
2023-09-06 15:14:26 -07:00
"gas", dynamicTx.Gas,
"gasTipCap", dynamicTx.GasTipCap,
"gasFeeCap", dynamicTx.GasFeeCap,
)
2023-12-12 16:23:39 -08:00
return &addr, tx, nil
2023-07-12 09:49:37 -07:00
}
2023-12-12 16:23:39 -08:00
func (p *Provider) sign(ctx context.Context, from *common.Address, tx *types.Transaction) (*types.Transaction, error) {
2023-07-12 09:49:37 -07:00
if p.walletConfig.SignerMethod == "static" {
log.Debug("using static signer")
privateKey, err := crypto.HexToECDSA(p.walletConfig.PrivateKey)
if err != nil {
2023-12-12 16:43:42 -08:00
log.Error("failed to parse private key", "err", err)
2023-07-12 09:49:37 -07:00
return nil, err
}
return types.SignTx(tx, types.LatestSignerForChainID(&p.walletConfig.ChainID), privateKey)
} else if p.walletConfig.SignerMethod == "signer" {
tlsConfig := tls.CLIConfig{
TLSCaCert: p.signerConfig.TLSCaCert,
TLSCert: p.signerConfig.TLSCert,
TLSKey: p.signerConfig.TLSKey,
}
client, err := iclients.NewSignerClient(p.name, log.Root(), p.signerConfig.URL, tlsConfig)
2023-12-12 16:43:42 -08:00
if err != nil || client == nil {
log.Error("failed to create signer client", "err", err)
2023-07-12 09:49:37 -07:00
}
if client == nil {
return nil, errors.New("could not initialize signer client")
}
2023-12-12 16:23:39 -08:00
signedTx, err := client.SignTransaction(ctx, &p.walletConfig.ChainID, from, tx)
2023-07-12 09:49:37 -07:00
if err != nil {
return nil, err
}
return signedTx, nil
} else {
return nil, errors.New("invalid signer method")
}
}