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

Retry mining transactions #291

Draft
wants to merge 24 commits into
base: miner_opt1
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion cmd/es-node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"math/big"
"os"

opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
Expand Down Expand Up @@ -204,7 +205,7 @@ func NewMinerConfig(ctx *cli.Context, client *ethclient.Client, l1Contract commo
return &minerConfig, nil
}

func NewSignerConfig(ctx *cli.Context) (signer.SignerFactory, common.Address, error) {
func NewSignerConfig(ctx *cli.Context) (opcrypto.SignerFactory, common.Address, error) {
signerConfig := signer.ReadCLIConfig(ctx)
if err := signerConfig.Check(); err != nil {
return nil, common.Address{}, fmt.Errorf("invalid siger flags: %w", err)
Expand Down
6 changes: 3 additions & 3 deletions ethstorage/eth/polling_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,13 @@ func (w *PollingClient) GetKvMetas(kvIndices []uint64, blockNumber int64) ([][32
return res[0].([][32]byte), nil
}

func (w *PollingClient) GetMiningReward(shard uint64, blockNumber int64) (*big.Int, error) {
func (w *PollingClient) GetMiningReward(shard, blockNumber uint64) (*big.Int, error) {
h := crypto.Keccak256Hash([]byte(`miningReward(uint256,uint256)`))
uint256Type, _ := abi.NewType("uint256", "", nil)
dataField, err := abi.Arguments{
{Type: uint256Type},
{Type: uint256Type},
}.Pack(new(big.Int).SetUint64(shard), new(big.Int).SetInt64(blockNumber))
}.Pack(new(big.Int).SetUint64(shard), new(big.Int).SetUint64(blockNumber))
if err != nil {
return nil, err
}
Expand All @@ -313,7 +313,7 @@ func (w *PollingClient) GetMiningReward(shard uint64, blockNumber int64) (*big.I
To: &w.esContract,
Data: calldata,
}
bs, err := w.Client.CallContract(context.Background(), callMsg, new(big.Int).SetInt64(blockNumber))
bs, err := w.Client.CallContract(context.Background(), callMsg, new(big.Int).SetUint64(blockNumber))
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions ethstorage/miner/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"path/filepath"
"runtime"

opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum/go-ethereum/common"
"github.com/ethstorage/go-ethstorage/ethstorage/signer"
)

type Config struct {
Expand All @@ -34,7 +34,7 @@ type Config struct {
ZKProverMode uint64
ZKProverImpl uint64
ThreadsPerShard uint64
SignerFnFactory signer.SignerFactory
SignerFnFactory opcrypto.SignerFactory
SignerAddr common.Address
MinimumProfit *big.Int
}
Expand Down
77 changes: 77 additions & 0 deletions ethstorage/miner/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package miner

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

opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/params"
"github.com/ethstorage/go-ethstorage/ethstorage/miner/txmgr"
)

func defaultTxMgrConfig(chainID *big.Int, l1Addr string, signerFactory opcrypto.SignerFactory) (txmgr.Config, error) {
cfg := txmgr.CLIConfig{
L1RPCURL: l1Addr,
// Number of confirmations which we will wait after sending a transaction
NumConfirmations: uint64(2),
// Number of ErrNonceTooLow observations required to give up on a tx at a particular nonce without receiving confirmation
SafeAbortNonceTooLowCount: uint64(3),
// The multiplier applied to fee suggestions to put a hard limit on fee increases
FeeLimitMultiplier: uint64(5),
// Minimum threshold (in Wei) at which the FeeLimitMultiplier takes effect.
FeeLimitThresholdGwei: 100.0,
// Duration we will wait before resubmitting a transaction to L1
ResubmissionTimeout: 24 * time.Second,
// NetworkTimeout is the allowed duration for a single network request.
NetworkTimeout: 10 * time.Second,
// Timeout for aborting a tx send if the tx does not make it to the mempool.
TxNotInMempoolTimeout: blockTooOldTimeout * time.Second,
// TxSendTimeout is how long to wait for sending a transaction.
TxSendTimeout: blockTooOldTimeout * time.Second,
// Frequency to poll for receipts
ReceiptQueryInterval: 12 * time.Second,
}
ctx, cancel := context.WithTimeout(context.Background(), cfg.NetworkTimeout)
defer cancel()
l1, err := ethclient.DialContext(ctx, cfg.L1RPCURL)
if err != nil {
return txmgr.Config{}, fmt.Errorf("could not dial eth client: %w", err)
}
// convert float GWei value into integer Wei value
feeLimitThreshold, _ := new(big.Float).Mul(
big.NewFloat(cfg.FeeLimitThresholdGwei),
big.NewFloat(params.GWei)).
Int(nil)

return txmgr.Config{
Backend: l1,
ResubmissionTimeout: cfg.ResubmissionTimeout,
FeeLimitMultiplier: cfg.FeeLimitMultiplier,
FeeLimitThreshold: feeLimitThreshold,
ChainID: chainID,
TxSendTimeout: cfg.TxSendTimeout,
TxNotInMempoolTimeout: cfg.TxNotInMempoolTimeout,
NetworkTimeout: cfg.NetworkTimeout,
ReceiptQueryInterval: cfg.ReceiptQueryInterval,
NumConfirmations: cfg.NumConfirmations,
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount,
Signer: signerFactory(chainID),
}, nil
}

// https://github.com/ethereum/go-ethereum/issues/21221#issuecomment-805852059
func weiToEther(wei *big.Int) *big.Float {
f := new(big.Float)
f.SetPrec(236) // IEEE 754 octuple-precision binary floating-point format: binary256
f.SetMode(big.ToNearestEven)
if wei == nil {
return f.SetInt64(0)
}
fWei := new(big.Float)
fWei.SetPrec(236) // IEEE 754 octuple-precision binary floating-point format: binary256
fWei.SetMode(big.ToNearestEven)
return f.Quo(fWei.SetInt(wei), big.NewFloat(params.Ether))
}
155 changes: 8 additions & 147 deletions ethstorage/miner/l1_mining_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package miner

import (
"context"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum"
Expand All @@ -20,22 +19,19 @@ import (
"github.com/ethstorage/go-ethstorage/ethstorage/eth"
)

const (
gasBufferRatio = 1.2
)

var (
mineSig = crypto.Keccak256Hash([]byte(`mine(uint256,uint256,address,uint256,bytes32[],uint256[],bytes,bytes[],bytes[])`))
)

func NewL1MiningAPI(l1 *eth.PollingClient, rc *eth.RandaoClient, lg log.Logger) *l1MiningAPI {
return &l1MiningAPI{l1, rc, lg}
func NewL1MiningAPI(l1 *eth.PollingClient, rc *eth.RandaoClient, l1URL string, lg log.Logger) *l1MiningAPI {
return &l1MiningAPI{l1, rc, l1URL, lg}
}

type l1MiningAPI struct {
*eth.PollingClient
rc *eth.RandaoClient
lg log.Logger
rc *eth.RandaoClient
l1URL string
lg log.Logger
}

func (m *l1MiningAPI) GetMiningInfo(ctx context.Context, contract common.Address, shardIdx uint64) (*miningInfo, error) {
Expand Down Expand Up @@ -83,81 +79,7 @@ func (m *l1MiningAPI) GetDataHashes(ctx context.Context, contract common.Address
return hashes, nil
}

func (m *l1MiningAPI) SubmitMinedResult(ctx context.Context, contract common.Address, rst result, cfg Config) (common.Hash, error) {
m.lg.Debug("Submit mined result", "shard", rst.startShardId, "block", rst.blockNumber, "nonce", rst.nonce)
calldata, err := m.composeCalldata(ctx, rst)
if err != nil {
m.lg.Error("Failed to compose calldata", "error", err)
return common.Hash{}, err
}
tip, gasFeeCap, predictedGasPrice, err := m.suggestGasPrices(ctx, cfg)
if err != nil {
m.lg.Error("Failed to suggest gas prices", "error", err)
return common.Hash{}, err
}
estimatedGas, err := m.EstimateGas(ctx, ethereum.CallMsg{
From: cfg.SignerAddr,
To: &contract,
GasTipCap: tip,
GasFeeCap: gasFeeCap,
Value: common.Big0,
Data: calldata,
})
if err != nil {
m.lg.Error("Estimate gas failed", "error", err.Error())
return common.Hash{}, fmt.Errorf("failed to estimate gas: %w", err)
}
m.lg.Info("Estimated gas done", "gas", estimatedGas)
cost := new(big.Int).Mul(new(big.Int).SetUint64(estimatedGas), predictedGasPrice)
reward, err := m.GetMiningReward(rst.startShardId, rst.blockNumber.Int64())
if err != nil {
m.lg.Error("Query mining reward failed", "error", err.Error())
return common.Hash{}, err
}
profit := new(big.Int).Sub(reward, cost)
m.lg.Info("Estimated reward and cost (in ether)", "reward", weiToEther(reward), "cost", weiToEther(cost), "profit", weiToEther(profit))
if profit.Cmp(cfg.MinimumProfit) == -1 {
m.lg.Warn("Will drop the tx: the profit will not meet expectation",
"profitEstimated", weiToEther(profit),
"minimumProfit", weiToEther(cfg.MinimumProfit),
)
return common.Hash{}, errDropped
}

sign := cfg.SignerFnFactory(m.NetworkID)
nonce, err := m.NonceAt(ctx, cfg.SignerAddr, big.NewInt(rpc.LatestBlockNumber.Int64()))
if err != nil {
m.lg.Error("Query nonce failed", "error", err.Error())
return common.Hash{}, err
}
m.lg.Debug("Query nonce done", "nonce", nonce)
gas := uint64(float64(estimatedGas) * gasBufferRatio)
rawTx := &types.DynamicFeeTx{
ChainID: m.NetworkID,
Nonce: nonce,
GasTipCap: tip,
GasFeeCap: gasFeeCap,
Gas: gas,
To: &contract,
Value: common.Big0,
Data: calldata,
}
signedTx, err := sign(ctx, cfg.SignerAddr, types.NewTx(rawTx))
if err != nil {
m.lg.Error("Sign tx error", "error", err)
return common.Hash{}, err
}
err = m.SendTransaction(ctx, signedTx)
if err != nil {
m.lg.Error("Send tx failed", "txNonce", nonce, "gasFeeCap", gasFeeCap, "error", err)
return common.Hash{}, err
}
m.lg.Info("Submit mined result done", "shard", rst.startShardId, "block", rst.blockNumber,
"nonce", rst.nonce, "txSigner", cfg.SignerAddr.Hex(), "hash", signedTx.Hash().Hex())
return signedTx.Hash(), nil
}

func (m *l1MiningAPI) getRandaoProof(ctx context.Context, blockNumber *big.Int) ([]byte, error) {
func (m *l1MiningAPI) GetRandaoProof(ctx context.Context, blockNumber *big.Int) ([]byte, error) {
var caller interface {
HeaderByNumber(context.Context, *big.Int) (*types.Header, error)
}
Expand All @@ -179,67 +101,6 @@ func (m *l1MiningAPI) getRandaoProof(ctx context.Context, blockNumber *big.Int)
return headerRlp, nil
}

func (m *l1MiningAPI) composeCalldata(ctx context.Context, rst result) ([]byte, error) {
headerRlp, err := m.getRandaoProof(ctx, rst.blockNumber)
if err != nil {
m.lg.Error("Failed to get randao proof", "error", err)
return nil, err
}
uint256Type, _ := abi.NewType("uint256", "", nil)
uint256Array, _ := abi.NewType("uint256[]", "", nil)
addrType, _ := abi.NewType("address", "", nil)
bytes32Array, _ := abi.NewType("bytes32[]", "", nil)
bytesArray, _ := abi.NewType("bytes[]", "", nil)
bytesType, _ := abi.NewType("bytes", "", nil)
dataField, _ := abi.Arguments{
{Type: uint256Type},
{Type: uint256Type},
{Type: addrType},
{Type: uint256Type},
{Type: bytes32Array},
{Type: uint256Array},
{Type: bytesType},
{Type: bytesArray},
{Type: bytesArray},
}.Pack(
rst.blockNumber,
new(big.Int).SetUint64(rst.startShardId),
rst.miner,
new(big.Int).SetUint64(rst.nonce),
rst.encodedData,
rst.masks,
headerRlp,
rst.inclusiveProofs,
rst.decodeProof,
)
calldata := append(mineSig[0:4], dataField...)
return calldata, nil
}

func (m *l1MiningAPI) suggestGasPrices(ctx context.Context, cfg Config) (*big.Int, *big.Int, *big.Int, error) {
tip := cfg.PriorityGasPrice
if tip == nil || tip.Cmp(common.Big0) == 0 {
suggested, err := m.SuggestGasTipCap(ctx)
if err != nil {
m.lg.Error("Query gas tip cap failed", "error", err.Error())
suggested = common.Big0
}
tip = suggested
m.lg.Info("Query gas tip cap done", "gasTipGap", tip)
}
gasFeeCap := cfg.GasPrice
predictedGasPrice := gasFeeCap
if gasFeeCap == nil || gasFeeCap.Cmp(common.Big0) == 0 {
blockHeader, err := m.HeaderByNumber(ctx, nil)
if err != nil {
m.lg.Error("Failed to get block header", "error", err)
return nil, nil, nil, err
}
// Doubling the base fee to ensure the transaction will remain marketable for six consecutive 100% full blocks.
gasFeeCap = new(big.Int).Add(tip, new(big.Int).Mul(blockHeader.BaseFee, common.Big2))
// Use `tip + base fee` as predicted gas price that will be used to evaluate the mining profit
predictedGasPrice = new(big.Int).Add(tip, blockHeader.BaseFee)
m.lg.Info("Compute gas fee cap done", "gasFeeCap", gasFeeCap, "predictedGasPrice", predictedGasPrice)
}
return tip, gasFeeCap, predictedGasPrice, nil
func (m *l1MiningAPI) L1Info() (*big.Int, string) {
return m.NetworkID, m.l1URL
}
7 changes: 6 additions & 1 deletion ethstorage/miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"math/big"
"sync"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb"
Expand All @@ -23,9 +24,13 @@ type L1API interface {
TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error)
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
GetMiningInfo(ctx context.Context, contract common.Address, shardIdx uint64) (*miningInfo, error)
SubmitMinedResult(ctx context.Context, contract common.Address, rst result, config Config) (common.Hash, error)
GetDataHashes(ctx context.Context, contract common.Address, kvIdxes []uint64) ([]common.Hash, error)
GetMiningReward(shard, blockNumber uint64) (*big.Int, error)
GetRandaoProof(ctx context.Context, blockNumber *big.Int) ([]byte, error)
BlockNumber(ctx context.Context) (uint64, error)
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
L1Info() (*big.Int, string)
}

type MiningProver interface {
Expand Down
Loading