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

Improve data cost estimation when full tx hurts compression ratio #1058

Merged
merged 4 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
62 changes: 47 additions & 15 deletions arbos/l1pricing/l1pricing.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
package l1pricing

import (
"encoding/binary"
"errors"
"fmt"
"math/big"
"sync/atomic"

"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"

"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
Expand Down Expand Up @@ -645,27 +647,57 @@ func (ps *L1PricingState) GetPosterInfo(tx *types.Transaction, poster common.Add
return am.BigMulByUint(pricePerUnit, units), units
}

const TxFixedCostEstimate = 140 // assumed maximum size in bytes of a typical RLP-encoded tx, not including its calldata
// We don't have the full tx in gas estimation, so we assume it might be a bit bigger in practice.
const estimationPaddingUnits = 16 * params.TxDataNonZeroGasEIP2028
const estimationPaddingBasisPoints = 100

var randomNonce = binary.BigEndian.Uint64(crypto.Keccak256([]byte("Nonce"))[:8])
var randomGasTipCap = new(big.Int).SetBytes(crypto.Keccak256([]byte("GasTipCap"))[:4])
var randomGasFeeCap = new(big.Int).SetBytes(crypto.Keccak256([]byte("GasFeeCap"))[:4])
var randV = arbmath.BigMulByUint(params.ArbitrumOneChainConfig().ChainID, 3)
var randR = crypto.Keccak256Hash([]byte("R")).Big()
var randS = crypto.Keccak256Hash([]byte("S")).Big()

func makeFakeTxForMessage(message core.Message) *types.Transaction {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it may be worth noting that this fake tx is invalid (signature doesn't check out, intentionally large field values, even the wrong tx type sometimes, etc)

nonce := message.Nonce()
if nonce == 0 {
nonce = randomNonce
}
gasTipCap := message.GasTipCap()
if gasTipCap.Sign() == 0 {
gasTipCap = randomGasTipCap
}
gasFeeCap := message.GasFeeCap()
if gasFeeCap.Sign() == 0 {
gasFeeCap = randomGasFeeCap
}
return types.NewTx(&types.DynamicFeeTx{
Nonce: nonce,
GasTipCap: gasTipCap,
GasFeeCap: gasFeeCap,
Gas: message.Gas(),
To: message.To(),
Value: message.Value(),
Data: message.Data(),
AccessList: message.AccessList(),
V: randV,
R: randR,
S: randS,
})
}

func (ps *L1PricingState) PosterDataCost(message core.Message, poster common.Address) (*big.Int, uint64) {
if tx := message.UnderlyingTransaction(); tx != nil {
tx := message.UnderlyingTransaction()
if tx != nil {
return ps.GetPosterInfo(tx, poster)
}

if poster != BatchPosterAddress {
return common.Big0, 0
}

byteCount, err := byteCountAfterBrotli0(message.Data())
if err != nil {
panic(fmt.Sprintf("failed to compress tx: %v", err))
}

// Approximate the l1 fee charged for posting this tx's calldata
l1Bytes := byteCount + TxFixedCostEstimate
// Otherwise, we don't have an underlying transaction, so we're likely in gas estimation.
// We'll instead make a fake tx from the message info we do have, and then pad our cost a bit to be safe.
tx = makeFakeTxForMessage(message)
units := ps.getPosterUnitsWithoutCache(tx, poster)
units = arbmath.UintMulByBips(units+estimationPaddingUnits, arbmath.OneInBips+estimationPaddingBasisPoints)
pricePerUnit, _ := ps.PricePerUnit()

units := l1Bytes * params.TxDataNonZeroGasEIP2028
return am.BigMulByUint(pricePerUnit, units), units
}

Expand Down
38 changes: 0 additions & 38 deletions arbos/l1pricing/l1pricing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,15 @@
package l1pricing

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

am "github.com/offchainlabs/nitro/util/arbmath"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/offchainlabs/nitro/arbos/burn"
"github.com/offchainlabs/nitro/arbos/storage"
)

func TestTxFixedCost(t *testing.T) {
maxChainId := am.UintToBig(math.MaxUint64)
maxValue := big.NewInt(1_000_000)
maxValue.Mul(maxValue, big.NewInt(params.Ether))
var address common.Address
for i := range address {
address[i] = 0xFF
}
maxSigVal := big.NewInt(2)
maxSigVal.Exp(maxSigVal, big.NewInt(256), nil)
maxSigVal.Sub(maxSigVal, common.Big1)
maxGasPrice := big.NewInt(1000 * params.GWei)
largeTx := types.NewTx(&types.DynamicFeeTx{
ChainID: maxChainId,
Nonce: 1 << 32,
GasTipCap: maxGasPrice,
GasFeeCap: maxGasPrice,
Gas: 100_000_000,
To: &address,
Value: maxValue,
Data: []byte{},
AccessList: []types.AccessTuple{},
V: common.Big1,
R: maxSigVal,
S: maxSigVal,
})
largeTxEncoded, err := largeTx.MarshalBinary()
Require(t, err)

if len(largeTxEncoded) > TxFixedCostEstimate {
Fail(t, "large tx is", len(largeTxEncoded), "bytes but tx fixed cost is", TxFixedCostEstimate)
}
}

func TestL1PriceUpdate(t *testing.T) {
sto := storage.NewMemoryBacked(burn.NewSystemBurner(nil, false))
err := InitializeL1PricingState(sto, common.Address{})
Expand Down
10 changes: 6 additions & 4 deletions precompiles/ArbGasInfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ type ArbGasInfo struct {

var storageArbGas = big.NewInt(int64(storage.StorageWriteCost))

const AssumedSimpleTxSize = 140
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to think about this later, but definitely not now


// Get prices in wei when using the provided aggregator
func (con ArbGasInfo) GetPricesInWeiWithAggregator(
c ctx,
Expand All @@ -40,7 +42,7 @@ func (con ArbGasInfo) GetPricesInWeiWithAggregator(
weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028)

// the cost of a simple tx without calldata
perL2Tx := arbmath.BigMulByUint(weiForL1Calldata, l1pricing.TxFixedCostEstimate)
perL2Tx := arbmath.BigMulByUint(weiForL1Calldata, AssumedSimpleTxSize)

// nitro's compute-centric l2 gas pricing has no special compute component that rises independently
perArbGasBase, err := c.State.L2PricingState().MinBaseFeeWei()
Expand Down Expand Up @@ -73,7 +75,7 @@ func (con ArbGasInfo) _preVersion4_GetPricesInWeiWithAggregator(
weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028)

// the cost of a simple tx without calldata
perL2Tx := arbmath.BigMulByUint(weiForL1Calldata, l1pricing.TxFixedCostEstimate)
perL2Tx := arbmath.BigMulByUint(weiForL1Calldata, AssumedSimpleTxSize)

// nitro's compute-centric l2 gas pricing has no special compute component that rises independently
perArbGasBase := l2GasPrice
Expand Down Expand Up @@ -103,7 +105,7 @@ func (con ArbGasInfo) GetPricesInArbGasWithAggregator(c ctx, evm mech, aggregato

// aggregators compress calldata, so we must estimate accordingly
weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028)
weiPerL2Tx := arbmath.BigMulByUint(weiForL1Calldata, l1pricing.TxFixedCostEstimate)
weiPerL2Tx := arbmath.BigMulByUint(weiForL1Calldata, AssumedSimpleTxSize)
gasForL1Calldata := common.Big0
gasPerL2Tx := common.Big0
if l2GasPrice.Sign() > 0 {
Expand All @@ -128,7 +130,7 @@ func (con ArbGasInfo) _preVersion4_GetPricesInArbGasWithAggregator(c ctx, evm me
gasForL1Calldata = arbmath.BigDiv(weiForL1Calldata, l2GasPrice)
}

perL2Tx := big.NewInt(l1pricing.TxFixedCostEstimate)
perL2Tx := big.NewInt(AssumedSimpleTxSize)
return perL2Tx, gasForL1Calldata, storageArbGas, nil
}

Expand Down
4 changes: 4 additions & 0 deletions util/arbmath/bips.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ func IntMulByBips(value int64, bips Bips) int64 {
return value * int64(bips) / int64(OneInBips)
}

func UintMulByBips(value uint64, bips Bips) uint64 {
return value * uint64(bips) / uint64(OneInBips)
}

func SaturatingCastToBips(value uint64) Bips {
return Bips(SaturatingCast(value))
}