Skip to content

Commit

Permalink
eip-4844: small additions and modified gaspool (erigontech#7190)
Browse files Browse the repository at this point in the history
This PR contains very small EIP-4844 additions. GasPool is modified and
now it is a struct with 2 fields "gas" and "dataGas" (blobs are priced
in dataGas). ExcessDataGas block header field added. ExcessDataGas
needed to compute the data gas price. EIP-4844 helper functions are
added as well.
  • Loading branch information
racytech authored Mar 27, 2023
1 parent 540af96 commit 70bc7f7
Show file tree
Hide file tree
Showing 15 changed files with 406 additions and 54 deletions.
17 changes: 16 additions & 1 deletion cmd/rpcdaemon/commands/engine_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type ExecutionPayload struct {
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
ExcessDataGas *hexutil.Big `json:"excessDataGas"`
}

// GetPayloadV2Response represents the response of the getPayloadV2 method
Expand Down Expand Up @@ -248,6 +249,17 @@ func (e *EngineImpl) newPayload(version uint32, ctx context.Context, payload *Ex
ep.Version = 2
ep.Withdrawals = privateapi.ConvertWithdrawalsToRpc(payload.Withdrawals)
}
if version >= 3 && payload.ExcessDataGas != nil {
ep.Version = 3
var excessDataGas *uint256.Int
var overflow bool
excessDataGas, overflow = uint256.FromBig((*big.Int)(payload.ExcessDataGas))
if overflow {
log.Warn("NewPayload ExcessDataGas overflow")
return nil, fmt.Errorf("invalid request, excess data gas overflow")
}
ep.ExcessDataGas = gointerfaces.ConvertUint256IntToH256(excessDataGas)
}

res, err := e.api.EngineNewPayload(ctx, ep)
if err != nil {
Expand Down Expand Up @@ -286,7 +298,10 @@ func convertPayloadFromRpc(payload *types2.ExecutionPayload) *ExecutionPayload {
if payload.Version >= 2 {
res.Withdrawals = privateapi.ConvertWithdrawalsFromRpc(payload.Withdrawals)
}

if payload.Version >= 3 {
edg := gointerfaces.ConvertH256ToUint256Int(payload.ExcessDataGas).ToBig()
res.ExcessDataGas = (*hexutil.Big)(edg)
}
return res
}

Expand Down
80 changes: 80 additions & 0 deletions consensus/misc/eip4844.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package misc

import (
"fmt"
"math/big"

"github.com/ledgerwatch/erigon-lib/chain"

"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/params"
)

// CalcExcessDataGas implements calc_excess_data_gas from EIP-4844
func CalcExcessDataGas(parentExcessDataGas *big.Int, newBlobs int) *big.Int {
excessDataGas := new(big.Int)
if parentExcessDataGas != nil {
excessDataGas.Set(parentExcessDataGas)
}
consumedGas := big.NewInt(params.DataGasPerBlob)
consumedGas.Mul(consumedGas, big.NewInt(int64(newBlobs)))

excessDataGas.Add(excessDataGas, consumedGas)
targetGas := big.NewInt(params.TargetDataGasPerBlock)
if excessDataGas.Cmp(targetGas) < 0 {
return new(big.Int)
}
return new(big.Int).Set(excessDataGas.Sub(excessDataGas, targetGas))
}

// FakeExponential approximates factor * e ** (num / denom) using a taylor expansion
// as described in the EIP-4844 spec.
func FakeExponential(factor, num, denom *big.Int) *big.Int {
output := new(big.Int)
numAccum := new(big.Int).Mul(factor, denom)
for i := 1; numAccum.Sign() > 0; i++ {
output.Add(output, numAccum)
numAccum.Mul(numAccum, num)
iBig := big.NewInt(int64(i))
numAccum.Div(numAccum, iBig.Mul(iBig, denom))
}
return output.Div(output, denom)
}

// CountBlobs returns the number of blob transactions in txs
func CountBlobs(txs []types.Transaction) int {
var count int
for _, tx := range txs {
count += len(tx.GetDataHashes())
}
return count
}

// VerifyEip4844Header verifies that the header is not malformed
func VerifyEip4844Header(config *chain.Config, parent, header *types.Header) error {
if header.ExcessDataGas == nil {
return fmt.Errorf("header is missing excessDataGas")
}
return nil
}

// GetDataGasPrice implements get_data_gas_price from EIP-4844
func GetDataGasPrice(excessDataGas *big.Int) *big.Int {
return FakeExponential(big.NewInt(params.MinDataGasPrice), excessDataGas, big.NewInt(params.DataGasPriceUpdateFraction))
}
89 changes: 89 additions & 0 deletions consensus/misc/eip4844_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package misc

import (
"math/big"
"testing"

"github.com/ledgerwatch/erigon/params"
)

func TestFakeExponential(t *testing.T) {
var tests = []struct {
factor, num, denom int64
want int64
}{
// When num==0 the return value should always equal the value of factor
{1, 0, 1, 1},
{38493, 0, 1000, 38493},
{0, 1234, 2345, 0}, // should be 0
{1, 2, 1, 6}, // approximate 7.389
{1, 4, 2, 6},
{1, 3, 1, 16}, // approximate 20.09
{1, 6, 2, 18},
{1, 4, 1, 49}, // approximate 54.60
{1, 8, 2, 50},
{10, 8, 2, 542}, // approximate 540.598
{11, 8, 2, 596}, // approximate 600.58
{1, 5, 1, 136}, // approximate 148.4
{1, 5, 2, 11}, // approximate 12.18
{2, 5, 2, 23}, // approximate 24.36
}

for _, tt := range tests {
factor := big.NewInt(tt.factor)
num := big.NewInt(tt.num)
denom := big.NewInt(tt.denom)
result := FakeExponential(factor, num, denom)
//t.Logf("%v*e^(%v/%v): %v", factor, num, denom, result)
if tt.want != result.Int64() {
t.Errorf("got %v want %v", result, tt.want)
}
}
}

func TestCalcExcessDataGas(t *testing.T) {
var tests = []struct {
parentExcessDataGas int64
newBlobs int
want int64
}{
{0, 0, 0},
{0, 1, 0},
{0, params.TargetDataGasPerBlock / params.DataGasPerBlob, 0},
{0, (params.TargetDataGasPerBlock / params.DataGasPerBlob) + 1, params.DataGasPerBlob},
{100000, (params.TargetDataGasPerBlock / params.DataGasPerBlob) + 1, params.DataGasPerBlob + 100000},
{params.TargetDataGasPerBlock, 1, params.DataGasPerBlob},
{params.TargetDataGasPerBlock, 0, 0},
{params.TargetDataGasPerBlock, (params.TargetDataGasPerBlock / params.DataGasPerBlob), params.TargetDataGasPerBlock},
}

for _, tt := range tests {
parentExcessDataGas := big.NewInt(tt.parentExcessDataGas)
result := CalcExcessDataGas(parentExcessDataGas, tt.newBlobs)
if tt.want != result.Int64() {
t.Errorf("got %v want %v", result, tt.want)
}
}

// Test nil value for parentExcessDataGas
result := CalcExcessDataGas(nil, (params.TargetDataGasPerBlock/params.DataGasPerBlob)+1)
if result.Int64() != params.DataGasPerBlob {
t.Errorf("got %v want %v", result, params.DataGasPerBlob)
}
}
4 changes: 4 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ var (
// by a transaction is higher than what's left in the block.
ErrGasLimitReached = errors.New("gas limit reached")

// ErrDataGasLimitReached is returned by the gas pool if the amount of data gas required
// by a transaction is higher than what's left in the block.
ErrDataGasLimitReached = errors.New("data gas limit reached")

// ErrMaxInitCodeSizeExceeded is returned if creation transaction provides the init code bigger
// than init code size limit.
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
Expand Down
42 changes: 34 additions & 8 deletions core/gaspool.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,62 @@ import (

// GasPool tracks the amount of gas available during execution of the transactions
// in a block. The zero value is a pool with zero gas available.
type GasPool uint64
type GasPool struct {
gas, dataGas uint64
}

func (gp *GasPool) Reset(amount uint64) {
*gp = GasPool(amount)
gp.gas = amount
}

// AddGas makes gas available for execution.
func (gp *GasPool) AddGas(amount uint64) *GasPool {
if uint64(*gp) > math.MaxUint64-amount {
if gp.gas > math.MaxUint64-amount {
panic("gas pool pushed above uint64")
}
*(*uint64)(gp) += amount
gp.gas += amount
return gp
}

// SubGas deducts the given amount from the pool if enough gas is
// available and returns an error otherwise.
func (gp *GasPool) SubGas(amount uint64) error {
if uint64(*gp) < amount {
if gp.gas < amount {
return ErrGasLimitReached
}
*(*uint64)(gp) -= amount
gp.gas -= amount
return nil
}

// Gas returns the amount of gas remaining in the pool.
func (gp *GasPool) Gas() uint64 {
return uint64(*gp)
return gp.gas
}

// AddDataGas makes data gas available for execution.
func (gp *GasPool) AddDataGas(amount uint64) *GasPool {
if gp.dataGas > math.MaxUint64-amount {
panic("data gas pool pushed above uint64")
}
gp.dataGas += amount
return gp
}

// SubDataGas deducts the given amount from the pool if enough data gas is available and returns an
// error otherwise.
func (gp *GasPool) SubDataGas(amount uint64) error {
if gp.dataGas < amount {
return ErrDataGasLimitReached
}
gp.dataGas -= amount
return nil
}

// DataGas returns the amount of data gas remaining in the pool.
func (gp *GasPool) DataGas() uint64 {
return gp.dataGas
}

func (gp *GasPool) String() string {
return fmt.Sprintf("%d", *gp)
return fmt.Sprintf("gas: %d, data_gas: %d", gp.gas, gp.dataGas)
}
55 changes: 29 additions & 26 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ type Genesis struct {

// These fields are used for consensus tests. Please don't use them
// in actual genesis blocks.
Number uint64 `json:"number"`
GasUsed uint64 `json:"gasUsed"`
ParentHash libcommon.Hash `json:"parentHash"`
BaseFee *big.Int `json:"baseFeePerGas"`
Number uint64 `json:"number"`
GasUsed uint64 `json:"gasUsed"`
ParentHash libcommon.Hash `json:"parentHash"`
BaseFee *big.Int `json:"baseFeePerGas"`
ExcessDataGas *big.Int `json:"excessDataGas"`
}

// GenesisAlloc specifies the initial state that is part of the genesis block.
Expand Down Expand Up @@ -120,15 +121,16 @@ type GenesisAccount struct {

// field type overrides for gencodec
type genesisSpecMarshaling struct {
Nonce math.HexOrDecimal64
Timestamp math.HexOrDecimal64
ExtraData hexutil.Bytes
GasLimit math.HexOrDecimal64
GasUsed math.HexOrDecimal64
Number math.HexOrDecimal64
Difficulty *math.HexOrDecimal256
BaseFee *math.HexOrDecimal256
Alloc map[common.UnprefixedAddress]GenesisAccount
Nonce math.HexOrDecimal64
Timestamp math.HexOrDecimal64
ExtraData hexutil.Bytes
GasLimit math.HexOrDecimal64
GasUsed math.HexOrDecimal64
Number math.HexOrDecimal64
Difficulty *math.HexOrDecimal256
BaseFee *math.HexOrDecimal256
ExcessDataGas *math.HexOrDecimal256
Alloc map[common.UnprefixedAddress]GenesisAccount
}

type genesisAccountMarshaling struct {
Expand Down Expand Up @@ -335,19 +337,20 @@ func (g *Genesis) ToBlock(tmpDir string) (*types.Block, *state.IntraBlockState,
_ = g.Alloc //nil-check

head := &types.Header{
Number: new(big.Int).SetUint64(g.Number),
Nonce: types.EncodeNonce(g.Nonce),
Time: g.Timestamp,
ParentHash: g.ParentHash,
Extra: g.ExtraData,
GasLimit: g.GasLimit,
GasUsed: g.GasUsed,
Difficulty: g.Difficulty,
MixDigest: g.Mixhash,
Coinbase: g.Coinbase,
BaseFee: g.BaseFee,
AuRaStep: g.AuRaStep,
AuRaSeal: g.AuRaSeal,
Number: new(big.Int).SetUint64(g.Number),
Nonce: types.EncodeNonce(g.Nonce),
Time: g.Timestamp,
ParentHash: g.ParentHash,
Extra: g.ExtraData,
GasLimit: g.GasLimit,
GasUsed: g.GasUsed,
Difficulty: g.Difficulty,
MixDigest: g.Mixhash,
Coinbase: g.Coinbase,
BaseFee: g.BaseFee,
ExcessDataGas: g.ExcessDataGas,
AuRaStep: g.AuRaStep,
AuRaSeal: g.AuRaSeal,
}
if g.GasLimit == 0 {
head.GasLimit = params.GenesisGasLimit
Expand Down
Loading

0 comments on commit 70bc7f7

Please sign in to comment.