Skip to content

Commit

Permalink
Merge pull request #1058 from OffchainLabs/improve-data-cost-estimation
Browse files Browse the repository at this point in the history
Improve data cost estimation when full tx hurts compression ratio
  • Loading branch information
PlasmaPower authored Sep 1, 2022
2 parents ef15bab + f1d6603 commit c48b8be
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 57 deletions.
64 changes: 49 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,59 @@ 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()

// The returned tx will be invalid, likely for a number of reasons such as an invalid signature.
// It's only used to check how large it is after brotli level 0 compression.
func makeFakeTxForMessage(message core.Message) *types.Transaction {
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
2 changes: 2 additions & 0 deletions nodeInterface/NodeInterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,8 @@ func (n NodeInterface) GasEstimateComponents(

pricing := c.State.L1PricingState()

// Setting the gas will affect the poster data cost
args.Gas = &totalRaw
msg, err = args.ToMessage(gasCap, n.header, evm.StateDB.(*state.StateDB))
if err != nil {
return 0, 0, nil, nil, err
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

// 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))
}

0 comments on commit c48b8be

Please sign in to comment.