Skip to content

Commit

Permalink
Merge pull request #29 from 0xPolygonHermez/feature/add-blobs
Browse files Browse the repository at this point in the history
Feature/add blobs
  • Loading branch information
dpunish3r authored Mar 14, 2024
2 parents 063f395 + a0e7ebd commit e27581a
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 54 deletions.
25 changes: 25 additions & 0 deletions etherman/etherman.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type ethereumClient interface {
ethereum.ContractCaller
ethereum.GasEstimator
ethereum.GasPricer
ethereum.GasPricer1559
ethereum.PendingStateReader
ethereum.TransactionReader
ethereum.TransactionSender
Expand Down Expand Up @@ -163,6 +164,18 @@ func (etherMan *Client) EstimateGas(ctx context.Context, from common.Address, to
})
}

// EstimateGasBlobTx returns the estimated gas for the blob tx
func (etherMan *Client) EstimateGasBlobTx(ctx context.Context, from common.Address, to *common.Address, gasFeeCap *big.Int, gasTipCap *big.Int, value *big.Int, data []byte) (uint64, error) {
return etherMan.EthClient.EstimateGas(ctx, ethereum.CallMsg{
From: from,
To: to,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Value: value,
Data: data,
})
}

// CheckTxWasMined check if a tx was already mined
func (etherMan *Client) CheckTxWasMined(ctx context.Context, txHash common.Hash) (bool, *types.Receipt, error) {
receipt, err := etherMan.EthClient.TransactionReceipt(ctx, txHash)
Expand Down Expand Up @@ -279,3 +292,15 @@ func (etherMan *Client) getBlockNumber(ctx context.Context, blockNumber rpc.Bloc
}
return header.Number.Uint64(), nil
}

// GetHeaderByNumber returns a block header from the current canonical chain, if number is nil the latest header is returned
func (etherMan *Client) GetHeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
header, err := etherMan.EthClient.HeaderByNumber(ctx, number)
return header, err
}

// GetSuggestGasTipCap retrieves the currently suggested gas tip cap after 1559 to allow a timely execution of a transaction
func (etherMan *Client) GetSuggestGasTipCap(ctx context.Context) (*big.Int, error) {
gasTipCap, err := etherMan.EthClient.SuggestGasTipCap(ctx)
return gasTipCap, err
}
223 changes: 189 additions & 34 deletions ethtxmanager/ethtxmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import (
"github.com/0xPolygonHermez/zkevm-ethtx-manager/log"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)

const failureIntervalInSeconds = 5
Expand Down Expand Up @@ -126,6 +130,8 @@ func pendingL1Txs(URL string, from common.Address, httpHeaders map[string]string

data := common.Hex2Bytes(tx.Data)

// TODO: handle case of blob transaction

mTx := monitoredTx{
ID: types.NewTx(&types.LegacyTx{To: &to, Nonce: nonce.Uint64(), Value: value, Data: data}).Hash(),
From: common.HexToAddress(tx.From),
Expand Down Expand Up @@ -174,7 +180,7 @@ func (c *Client) getTxNonce(ctx context.Context, from common.Address) (uint64, e
}

// Add a transaction to be sent and monitored
func (c *Client) Add(ctx context.Context, to *common.Address, forcedNonce *uint64, value *big.Int, data []byte) (common.Hash, error) {
func (c *Client) Add(ctx context.Context, to *common.Address, forcedNonce *uint64, value *big.Int, data []byte, sidecar *types.BlobTxSidecar) (common.Hash, error) {
var nonce uint64
var err error

Expand All @@ -190,19 +196,6 @@ func (c *Client) Add(ctx context.Context, to *common.Address, forcedNonce *uint6
nonce = *forcedNonce
}

// get gas
gas, err := c.etherman.EstimateGas(ctx, c.from, to, value, data)
if err != nil {
err := fmt.Errorf("failed to estimate gas: %w, data: %v", err, common.Bytes2Hex(data))
log.Error(err.Error())
log.Debugf("failed to estimate gas for tx: from: %v, to: %v, value: %v", c.from.String(), to.String(), value.String())
if c.cfg.ForcedGas > 0 {
gas = c.cfg.ForcedGas
} else {
return common.Hash{}, err
}
}

// get gas price
gasPrice, err := c.suggestedGasPrice(ctx)
if err != nil {
Expand All @@ -211,13 +204,81 @@ func (c *Client) Add(ctx context.Context, to *common.Address, forcedNonce *uint6
return common.Hash{}, err
}

var gas uint64
var blobFeeCap *big.Int
var gasTipCap *big.Int

if sidecar != nil {
// blob gas price estimation
parentHeader, err := c.etherman.GetHeaderByNumber(ctx, nil)
if err != nil {
log.Errorf("failed to get parent header: %v", err)
return common.Hash{}, err
}

if parentHeader.ExcessBlobGas != nil && parentHeader.BlobGasUsed != nil {
parentExcessBlobGas := eip4844.CalcExcessBlobGas(*parentHeader.ExcessBlobGas, *parentHeader.BlobGasUsed)
blobFeeCap = eip4844.CalcBlobFee(parentExcessBlobGas)
} else {
log.Infof("legacy parent header no blob gas info")
blobFeeCap = eip4844.CalcBlobFee(0)
}

gasTipCap, err = c.etherman.GetSuggestGasTipCap(ctx)
if err != nil {
log.Errorf("failed to get gas tip cap: %v", err)
return common.Hash{}, err
}

// get gas
gas, err = c.etherman.EstimateGasBlobTx(ctx, c.from, to, gasPrice, gasTipCap, value, data)
if err != nil {
err := fmt.Errorf("failed to estimate gas blob tx: %w, data: %v", err, common.Bytes2Hex(data))
log.Error(err.Error())
log.Debugf("failed to estimate gas for blob tx: from: %v, to: %v, value: %v", c.from.String(), to.String(), value.String())
return common.Hash{}, err
}

// margin
const multiplier = 10
gasTipCap = gasTipCap.Mul(gasTipCap, big.NewInt(multiplier))
gasPrice = gasPrice.Mul(gasPrice, big.NewInt(multiplier))
blobFeeCap = blobFeeCap.Mul(blobFeeCap, big.NewInt(multiplier))
gas = gas * 12 / 10 //nolint:gomnd
} else {
// get gas
gas, err = c.etherman.EstimateGas(ctx, c.from, to, value, data)
if err != nil {
err := fmt.Errorf("failed to estimate gas: %w, data: %v", err, common.Bytes2Hex(data))
log.Error(err.Error())
log.Debugf("failed to estimate gas for tx: from: %v, to: %v, value: %v", c.from.String(), to.String(), value.String())
if c.cfg.ForcedGas > 0 {
gas = c.cfg.ForcedGas
} else {
return common.Hash{}, err
}
}
}

// Calculate id
tx := types.NewTx(&types.LegacyTx{
To: to,
Nonce: nonce,
Value: value,
Data: data,
})
var tx *types.Transaction
if sidecar == nil {
tx = types.NewTx(&types.LegacyTx{
To: to,
Nonce: nonce,
Value: value,
Data: data,
})
} else {
tx = types.NewTx(&types.BlobTx{
To: *to,
Nonce: nonce,
Value: uint256.MustFromBig(value),
Data: data,
BlobHashes: sidecar.BlobHashes(),
Sidecar: sidecar,
})
}

id := tx.Hash()

Expand All @@ -226,6 +287,9 @@ func (c *Client) Add(ctx context.Context, to *common.Address, forcedNonce *uint6
ID: id, From: c.from, To: to,
Nonce: nonce, Value: value, Data: data,
Gas: gas, GasPrice: gasPrice,
BlobSidecar: sidecar,
BlobGas: tx.BlobGas(),
BlobGasPrice: blobFeeCap, GasTipCap: gasTipCap,
Status: MonitoredTxStatusCreated,
History: make(map[common.Hash]bool),
}
Expand Down Expand Up @@ -693,19 +757,9 @@ func (c *Client) shouldContinueToMonitorThisTx(ctx context.Context, receipt type
// state of the blockchain
func (c *Client) reviewMonitoredTx(ctx context.Context, mTx *monitoredTx, mTxLogger *log.Logger) error {
mTxLogger.Debug("reviewing")
// get gas
gas, err := c.etherman.EstimateGas(ctx, mTx.From, mTx.To, mTx.Value, mTx.Data)
if err != nil {
err := fmt.Errorf("failed to estimate gas: %w", err)
mTxLogger.Errorf(err.Error())
return err
}

// check gas
if gas > mTx.Gas {
mTxLogger.Infof("monitored tx gas updated from %v to %v", mTx.Gas, gas)
mTx.Gas = gas
}
isBlobTx := mTx.BlobSidecar != nil
var err error
var gas uint64

// get gas price
gasPrice, err := c.suggestedGasPrice(ctx)
Expand All @@ -717,9 +771,63 @@ func (c *Client) reviewMonitoredTx(ctx context.Context, mTx *monitoredTx, mTxLog

// check gas price
if gasPrice.Cmp(mTx.GasPrice) == 1 {
mTxLogger.Infof("monitored tx gas price updated from %v to %v", mTx.GasPrice.String(), gasPrice.String())
mTxLogger.Infof("monitored tx (blob? %t) GasPrice updated from %v to %v", isBlobTx, mTx.GasPrice.String(), gasPrice.String())
mTx.GasPrice = gasPrice
}

// get gas
if mTx.BlobSidecar != nil {
// blob gas price estimation
parentHeader, err := c.etherman.GetHeaderByNumber(ctx, nil)
if err != nil {
log.Errorf("failed to get parent header: %v", err)
return err
}

var blobFeeCap *big.Int
if parentHeader.ExcessBlobGas != nil && parentHeader.BlobGasUsed != nil {
parentExcessBlobGas := eip4844.CalcExcessBlobGas(*parentHeader.ExcessBlobGas, *parentHeader.BlobGasUsed)
blobFeeCap = eip4844.CalcBlobFee(parentExcessBlobGas)
} else {
log.Infof("legacy parent header no blob gas info")
blobFeeCap = eip4844.CalcBlobFee(0)
}

gasTipCap, err := c.etherman.GetSuggestGasTipCap(ctx)
if err != nil {
log.Errorf("failed to get gas tip cap: %v", err)
return err
}

if gasTipCap.Cmp(mTx.GasTipCap) == 1 {
mTxLogger.Infof("monitored tx (blob? %t) GasTipCap updated from %v to %v", isBlobTx, mTx.GasTipCap, gasTipCap)
mTx.GasTipCap = gasTipCap
}
if blobFeeCap.Cmp(mTx.BlobGasPrice) == 1 {
mTxLogger.Infof("monitored tx (blob? %t) BlobFeeCap updated from %v to %v", isBlobTx, mTx.BlobGasPrice, blobFeeCap)
mTx.BlobGasPrice = blobFeeCap
}

gas, err = c.etherman.EstimateGasBlobTx(ctx, mTx.From, mTx.To, mTx.GasPrice, mTx.GasTipCap, mTx.Value, mTx.Data)
if err != nil {
err := fmt.Errorf("failed to estimate gas blob tx: %w", err)
mTxLogger.Errorf(err.Error())
return err
}
} else {
gas, err = c.etherman.EstimateGas(ctx, mTx.From, mTx.To, mTx.Value, mTx.Data)
if err != nil {
err := fmt.Errorf("failed to estimate gas: %w", err)
mTxLogger.Errorf(err.Error())
return err
}
}

// check gas
if gas > mTx.Gas {
mTxLogger.Infof("monitored tx (blob? %t) Gas updated from %v to %v", isBlobTx, mTx.Gas, gas)
mTx.Gas = gas
}
return nil
}

Expand Down Expand Up @@ -853,6 +961,53 @@ func (c *Client) ProcessPendingMonitoredTxs(ctx context.Context, resultHandler R
}
}

// EncodeBlobData encodes data into blob data type
func (c *Client) EncodeBlobData(data []byte) (kzg4844.Blob, error) {
dataLen := len(data)
if dataLen > params.BlobTxFieldElementsPerBlob*(params.BlobTxBytesPerFieldElement-1) {
log.Infof("blob data longer than allowed (length: %v, limit: %v)", dataLen, params.BlobTxFieldElementsPerBlob*(params.BlobTxBytesPerFieldElement-1))
return kzg4844.Blob{}, errors.New("blob data longer than allowed")
}

// 1 Blob = 4096 Field elements x 32 bytes/field element = 128 KB
elemSize := params.BlobTxBytesPerFieldElement

blob := kzg4844.Blob{}
fieldIndex := -1
for i := 0; i < len(data); i += (elemSize - 1) {
fieldIndex++
if fieldIndex == params.BlobTxFieldElementsPerBlob {
break
}
max := i + (elemSize - 1)
if max > len(data) {
max = len(data)
}
copy(blob[fieldIndex*elemSize+1:], data[i:max])
}
return blob, nil
}

// MakeBlobSidecar constructs a blob tx sidecar
func (c *Client) MakeBlobSidecar(blobs []kzg4844.Blob) *types.BlobTxSidecar {
var commitments []kzg4844.Commitment
var proofs []kzg4844.Proof

for _, blob := range blobs {
c, _ := kzg4844.BlobToCommitment(blob)
p, _ := kzg4844.ComputeBlobProof(blob, c)

commitments = append(commitments, c)
proofs = append(proofs, p)
}

return &types.BlobTxSidecar{
Blobs: blobs,
Commitments: commitments,
Proofs: proofs,
}
}

// createMonitoredTxLogger creates an instance of logger with all the important
// fields already set for a monitoredTx
func createMonitoredTxLogger(mTx monitoredTx) *log.Logger {
Expand Down
3 changes: 3 additions & 0 deletions ethtxmanager/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ type ethermanInterface interface {
PendingNonce(ctx context.Context, account common.Address) (uint64, error)
SuggestedGasPrice(ctx context.Context) (*big.Int, error)
EstimateGas(ctx context.Context, from common.Address, to *common.Address, value *big.Int, data []byte) (uint64, error)
EstimateGasBlobTx(ctx context.Context, from common.Address, to *common.Address, gasFeeCap *big.Int, gasTipCap *big.Int, value *big.Int, data []byte) (uint64, error)
CheckTxWasMined(ctx context.Context, txHash common.Hash) (bool, *types.Receipt, error)
SignTx(ctx context.Context, sender common.Address, tx *types.Transaction) (*types.Transaction, error)
GetRevertMessage(ctx context.Context, tx *types.Transaction) (string, error)
GetLatestBlockNumber(ctx context.Context) (uint64, error)
GetHeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
GetSuggestGasTipCap(ctx context.Context) (*big.Int, error)
}

type storageInterface interface {
Expand Down
2 changes: 1 addition & 1 deletion ethtxmanager/memstorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func NewMemStorage(persistenceFilename string) *MemStorage {
if persistenceFilename != "" {
// Check if the file exists
if _, err := os.Stat(persistenceFilename); os.IsNotExist(err) {
log.Warnf("Persistence file %s does not exist", persistenceFilename)
log.Infof("Persistence file %s does not exist", persistenceFilename)
} else {
ReadFile, err := os.ReadFile(persistenceFilename)
if err != nil {
Expand Down
Loading

0 comments on commit e27581a

Please sign in to comment.