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

170 lines
5.1 KiB
Go
Raw Normal View History

package provider
import (
"context"
"op-ufm/pkg/metrics"
iclients "op-ufm/pkg/metrics/clients"
2023-07-12 20:28:32 +03:00
"time"
2023-07-12 19:49:37 +03:00
"github.com/ethereum-optimism/optimism/op-service/tls"
2023-07-12 20:28:32 +03:00
"github.com/ethereum/go-ethereum"
2023-07-12 19:49:37 +03:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool"
2023-07-12 19:49:37 +03: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-07-12 22:20:23 +03:00
log.Debug("roundtrip", "provider", p.name)
2023-07-12 19:49:37 +03:00
client, err := iclients.Dial(p.name, p.config.URL)
2023-07-12 19:49:37 +03:00
if err != nil {
log.Error("cant dial to provider", "provider", p.name, "url", p.config.URL, "err", err)
return
2023-07-12 19:49:37 +03:00
}
nonce, err := client.PendingNonceAt(ctx, p.walletConfig.Address)
2023-07-12 19:49:37 +03:00
if err != nil {
log.Error("cant get nounce", "provider", p.name, "err", err)
return
2023-07-12 19:49:37 +03:00
}
txHash := common.Hash{}
attempt := 0
startedAt := time.Now()
for {
tx := p.createTx(nonce)
txHash = tx.Hash()
2023-07-12 19:49:37 +03:00
signedTx, err := p.sign(ctx, tx)
if err != nil {
log.Error("cant sign tx", "provider", p.name, "tx", tx, "err", err)
return
}
2023-07-12 19:49:37 +03:00
txHash = signedTx.Hash()
err = client.SendTransaction(ctx, signedTx)
if err != nil {
if err.Error() == txpool.ErrAlreadyKnown.Error() || err.Error() == core.ErrNonceTooLow.Error() {
if time.Since(startedAt) >= time.Duration(p.config.SendTransactionRetryTimeout) {
log.Error("send transaction timed out (known already)", "provider", p.name, "hash", txHash.Hex(), "elapsed", time.Since(startedAt), "attempt", attempt, "nonce", nonce)
metrics.RecordError(p.name, "ethclient.SendTransaction.nonce")
return
}
log.Warn("tx already known, incrementing nonce and trying again", "provider", p.name, "nonce", nonce)
time.Sleep(time.Duration(p.config.SendTransactionRetryInterval))
nonce++
attempt++
if attempt%10 == 0 {
log.Debug("retrying send transaction...", "provider", p.name, "attempt", attempt, "nonce", nonce, "elapsed", time.Since(startedAt))
}
} else {
log.Error("cant send transaction", "provider", p.name, "err", err)
return
}
} else {
break
}
2023-07-12 19:49:37 +03:00
}
log.Info("transaction sent", "provider", p.name, "hash", txHash.Hex(), "nonce", nonce)
2023-07-12 20:28:32 +03:00
2023-07-12 22:20:23 +03:00
// add to pool
2023-07-12 20:28:32 +03:00
sentAt := time.Now()
2023-07-12 22:20:23 +03:00
p.txPool.M.Lock()
p.txPool.Transactions[txHash.Hex()] = &TransactionState{
Hash: txHash,
ProviderSentTo: p.name,
SentAt: sentAt,
SeenBy: make(map[string]time.Time),
2023-07-12 22:20:23 +03:00
}
p.txPool.M.Unlock()
2023-07-12 20:28:32 +03:00
var receipt *types.Receipt
attempt = 0
2023-07-12 20:28:32 +03:00
for receipt == nil {
if time.Since(sentAt) >= time.Duration(p.config.ReceiptRetrievalTimeout) {
2023-07-12 22:20:23 +03:00
log.Error("receipt retrieval timed out", "provider", p.name, "hash", "elapsed", time.Since(sentAt))
return
2023-07-12 20:28:32 +03:00
}
time.Sleep(time.Duration(p.config.ReceiptRetrievalInterval))
if attempt%10 == 0 {
log.Debug("checking for receipt...", "provider", p.name, "attempt", attempt, "elapsed", time.Since(sentAt))
2023-07-12 20:28:32 +03:00
}
receipt, err = client.TransactionReceipt(ctx, txHash)
2023-07-12 20:28:32 +03:00
if err != nil && !errors.Is(err, ethereum.NotFound) {
log.Error("cant get receipt for transaction", "provider", p.name, "hash", txHash.Hex(), "err", err)
return
2023-07-12 20:28:32 +03:00
}
attempt++
}
roundtrip := time.Since(sentAt)
metrics.RecordRoundTripLatency(p.name, roundtrip)
metrics.RecordGasUsed(p.name, receipt.GasUsed)
2023-07-12 20:28:32 +03:00
log.Info("got transaction receipt", "hash", txHash.Hex(),
"roundtrip", roundtrip,
"provider", p.name,
2023-07-12 20:28:32 +03:00
"blockNumber", receipt.BlockNumber,
"blockHash", receipt.BlockHash,
"gasUsed", receipt.GasUsed)
2023-07-12 19:49:37 +03:00
}
func (p *Provider) createTx(nonce uint64) *types.Transaction {
toAddress := common.HexToAddress(p.walletConfig.Address)
var data []byte
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: &p.walletConfig.ChainID,
Nonce: nonce,
GasFeeCap: &p.walletConfig.GasFeeCap,
GasTipCap: &p.walletConfig.GasTipCap,
Gas: p.walletConfig.GasLimit,
To: &toAddress,
Value: &p.walletConfig.TxValue,
Data: data,
})
return tx
}
func (p *Provider) sign(ctx context.Context, tx *types.Transaction) (*types.Transaction, error) {
if p.walletConfig.SignerMethod == "static" {
log.Debug("using static signer")
privateKey, err := crypto.HexToECDSA(p.walletConfig.PrivateKey)
if err != nil {
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-07-12 19:49:37 +03:00
log.Debug("signerclient", "client", client, "err", err)
if err != nil {
return nil, err
}
if client == nil {
return nil, errors.New("could not initialize signer client")
}
signedTx, err := client.SignTransaction(ctx, &p.walletConfig.ChainID, tx)
if err != nil {
return nil, err
}
return signedTx, nil
} else {
return nil, errors.New("invalid signer method")
}
}