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

core: 4844 opcode and precompile #27356

Merged
merged 3 commits into from Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/evm/runner.go
Expand Up @@ -128,6 +128,7 @@ func runCmd(ctx *cli.Context) error {
receiver = common.BytesToAddress([]byte("receiver"))
genesisConfig *core.Genesis
preimages = ctx.Bool(DumpFlag.Name)
blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests
)
if ctx.Bool(MachineFlag.Name) {
tracer = logger.NewJSONLogger(logconfig, os.Stdout)
Expand Down Expand Up @@ -217,6 +218,7 @@ func runCmd(ctx *cli.Context) error {
Time: genesisConfig.Timestamp,
Coinbase: genesisConfig.Coinbase,
BlockNumber: new(big.Int).SetUint64(genesisConfig.Number),
BlobHashes: blobHashes,
EVMConfig: vm.Config{
Tracer: tracer,
},
Expand Down
5 changes: 3 additions & 2 deletions core/evm.go
Expand Up @@ -72,8 +72,9 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
// NewEVMTxContext creates a new transaction context for a single transaction.
func NewEVMTxContext(msg *Message) vm.TxContext {
return vm.TxContext{
Origin: msg.From,
GasPrice: new(big.Int).Set(msg.GasPrice),
Origin: msg.From,
GasPrice: new(big.Int).Set(msg.GasPrice),
BlobHashes: msg.BlobHashes,
}
}

Expand Down
2 changes: 2 additions & 0 deletions core/state_transition.go
Expand Up @@ -135,6 +135,7 @@ type Message struct {
GasTipCap *big.Int
Data []byte
AccessList types.AccessList
BlobHashes []common.Hash

// When SkipAccountChecks is true, the message nonce is not checked against the
// account nonce in state. It also disables checking that the sender is an EOA.
Expand All @@ -155,6 +156,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
Data: tx.Data(),
AccessList: tx.AccessList(),
SkipAccountChecks: false,
BlobHashes: tx.BlobHashes(),
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
Expand Down
87 changes: 87 additions & 0 deletions core/vm/contracts.go
Expand Up @@ -20,6 +20,7 @@ import (
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -28,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/blake2b"
"github.com/ethereum/go-ethereum/crypto/bls12381"
"github.com/ethereum/go-ethereum/crypto/bn256"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params"
"golang.org/x/crypto/ripemd160"
)
Expand Down Expand Up @@ -90,6 +92,21 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{9}): &blake2F{},
}

// PrecompiledContractsCancun contains the default set of pre-compiled Ethereum
// contracts used in the Cancun release.
var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{20}): &kzgPointEvaluation{},
}

// PrecompiledContractsBLS contains the set of pre-compiled Ethereum
// contracts specified in EIP-2537. These are exported for testing purposes.
var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
Expand All @@ -105,6 +122,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
}

var (
PrecompiledAddressesCancun []common.Address
PrecompiledAddressesBerlin []common.Address
PrecompiledAddressesIstanbul []common.Address
PrecompiledAddressesByzantium []common.Address
Expand All @@ -124,11 +142,16 @@ func init() {
for k := range PrecompiledContractsBerlin {
PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k)
}
for k := range PrecompiledContractsCancun {
PrecompiledAddressesCancun = append(PrecompiledAddressesCancun, k)
}
}

// ActivePrecompiles returns the precompiles enabled with the current configuration.
func ActivePrecompiles(rules params.Rules) []common.Address {
switch {
case rules.IsCancun:
return PrecompiledAddressesCancun
case rules.IsBerlin:
return PrecompiledAddressesBerlin
case rules.IsIstanbul:
Expand Down Expand Up @@ -1048,3 +1071,67 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
// Encode the G2 point to 256 bytes
return g.EncodePoint(r), nil
}

// kzgPointEvaluation implements the EIP-4844 point evaluation precompile.
type kzgPointEvaluation struct{}

// RequiredGas estimates the gas required for running the point evaluation precompile.
func (b *kzgPointEvaluation) RequiredGas(input []byte) uint64 {
return params.BlobTxPointEvaluationPrecompileGas
}

const (
blobVerifyInputLength = 192 // Max input length for the point evaluation precompile.
blobCommitmentVersionKZG uint8 = 0x01 // Version byte for the point evaluation precompile.
Copy link
Member

Choose a reason for hiding this comment

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

This is part of the params package, shouldn't we use that?

blobPrecompileReturnValue = "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
)

var (
errBlobVerifyInvalidInputLength = errors.New("invalid input length")
errBlobVerifyMismatchedVersion = errors.New("mismatched versioned hash")
errBlobVerifyKZGProof = errors.New("error verifying kzg proof")
)

// Run executes the point evaluation precompile.
func (b *kzgPointEvaluation) Run(input []byte) ([]byte, error) {
if len(input) != blobVerifyInputLength {
return nil, errBlobVerifyInvalidInputLength
}
// versioned hash: first 32 bytes
var versionedHash common.Hash
copy(versionedHash[:], input[:])

var (
point kzg4844.Point
claim kzg4844.Claim
)
// Evaluation point: next 32 bytes
copy(point[:], input[32:])
// Expected output: next 32 bytes
copy(claim[:], input[64:])

// input kzg point: next 48 bytes
var commitment kzg4844.Commitment
copy(commitment[:], input[96:])
if kZGToVersionedHash(commitment) != versionedHash {
return nil, errBlobVerifyMismatchedVersion
}

// Proof: next 48 bytes
var proof kzg4844.Proof
copy(proof[:], input[144:])

if err := kzg4844.VerifyProof(commitment, point, claim, proof); err != nil {
return nil, fmt.Errorf("%w: %v", errBlobVerifyKZGProof, err)
}

return common.Hex2Bytes(blobPrecompileReturnValue), nil
}

// kZGToVersionedHash implements kzg_to_versioned_hash from EIP-4844
func kZGToVersionedHash(kzg kzg4844.Commitment) common.Hash {
h := sha256.Sum256(kzg[:])
h[0] = blobCommitmentVersionKZG

return h
}
2 changes: 2 additions & 0 deletions core/vm/contracts_test.go
Expand Up @@ -65,6 +65,7 @@ var allPrecompiles = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{16}): &bls12381Pairing{},
common.BytesToAddress([]byte{17}): &bls12381MapG1{},
common.BytesToAddress([]byte{18}): &bls12381MapG2{},
common.BytesToAddress([]byte{20}): &kzgPointEvaluation{},
}

// EIP-152 test vectors
Expand Down Expand Up @@ -311,6 +312,7 @@ func TestPrecompiledBLS12381G2MultiExp(t *testing.T) { testJson("blsG2MultiExp",
func TestPrecompiledBLS12381Pairing(t *testing.T) { testJson("blsPairing", "10", t) }
func TestPrecompiledBLS12381MapG1(t *testing.T) { testJson("blsMapG1", "11", t) }
func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "12", t) }
func TestPrecompiledPointEvaluation(t *testing.T) { testJson("pointEvaluation", "14", t) }

func BenchmarkPrecompiledBLS12381G1Add(b *testing.B) { benchJson("blsG1Add", "0a", b) }
func BenchmarkPrecompiledBLS12381G1Mul(b *testing.B) { benchJson("blsG1Mul", "0b", b) }
Expand Down
25 changes: 24 additions & 1 deletion core/vm/eips.go
Expand Up @@ -235,9 +235,32 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
return nil, nil
}

// ebnable3860 enables "EIP-3860: Limit and meter initcode"
// enable3860 enables "EIP-3860: Limit and meter initcode"
// https://eips.ethereum.org/EIPS/eip-3860
func enable3860(jt *JumpTable) {
jt[CREATE].dynamicGas = gasCreateEip3860
jt[CREATE2].dynamicGas = gasCreate2Eip3860
}

// opBlobHash implements the BLOBHASH opcode
func opBlobHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
index := scope.Stack.peek()
if index.LtUint64(uint64(len(interpreter.evm.TxContext.BlobHashes))) {
blobHash := interpreter.evm.TxContext.BlobHashes[index.Uint64()]
index.SetBytes32(blobHash[:])
} else {
index.Clear()
}
return nil, nil
}

// enable4844 applies EIP-4844 (DATAHASH opcode)
func enable4844(jt *JumpTable) {
// New opcode
jt[BLOBHASH] = &operation{
execute: opBlobHash,
constantGas: GasFastestStep,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
}
7 changes: 5 additions & 2 deletions core/vm/evm.go
Expand Up @@ -43,6 +43,8 @@ type (
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
switch {
case evm.chainRules.IsCancun:
precompiles = PrecompiledContractsCancun
case evm.chainRules.IsBerlin:
precompiles = PrecompiledContractsBerlin
case evm.chainRules.IsIstanbul:
Expand Down Expand Up @@ -81,8 +83,9 @@ type BlockContext struct {
// All fields can change between transactions.
type TxContext struct {
// Message information
Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE
Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE
BlobHashes []common.Hash // Provides information for BLOBHASH
}

// EVM is the Ethereum Virtual Machine base object and provides
Expand Down
42 changes: 42 additions & 0 deletions core/vm/instructions_test.go
Expand Up @@ -746,3 +746,45 @@ func TestRandom(t *testing.T) {
}
}
}

func TestBlobHash(t *testing.T) {
type testcase struct {
name string
idx uint64
expect common.Hash
hashes []common.Hash
}
var (
zero = common.Hash{0}
one = common.Hash{1}
two = common.Hash{2}
three = common.Hash{3}
)
for _, tt := range []testcase{
{name: "[{1}]", idx: 0, expect: one, hashes: []common.Hash{one}},
{name: "[1,{2},3]", idx: 2, expect: three, hashes: []common.Hash{one, two, three}},
{name: "out-of-bounds (empty)", idx: 10, expect: zero, hashes: []common.Hash{}},
{name: "out-of-bounds", idx: 25, expect: zero, hashes: []common.Hash{one, two, three}},
{name: "out-of-bounds (nil)", idx: 25, expect: zero, hashes: nil},
} {
var (
env = NewEVM(BlockContext{}, TxContext{BlobHashes: tt.hashes}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
)
stack.push(uint256.NewInt(tt.idx))
opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
}
actual := stack.pop()
expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.expect.Bytes()))
if overflow {
t.Errorf("Testcase %v: invalid overflow", tt.name)
}
if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual)
}
}
}
2 changes: 2 additions & 0 deletions core/vm/interpreter.go
Expand Up @@ -56,6 +56,8 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
// If jump table was not initialised we set the default one.
var table *JumpTable
switch {
case evm.chainRules.IsCancun:
table = &cancunInstructionSet
case evm.chainRules.IsShanghai:
table = &shanghaiInstructionSet
case evm.chainRules.IsMerge:
Expand Down
7 changes: 7 additions & 0 deletions core/vm/jump_table.go
Expand Up @@ -56,6 +56,7 @@ var (
londonInstructionSet = newLondonInstructionSet()
mergeInstructionSet = newMergeInstructionSet()
shanghaiInstructionSet = newShanghaiInstructionSet()
cancunInstructionSet = newCancunInstructionSet()
)

// JumpTable contains the EVM opcodes supported at a given fork.
Expand All @@ -79,6 +80,12 @@ func validate(jt JumpTable) JumpTable {
return jt
}

func newCancunInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet()
enable4844(&instructionSet) // BLOBHASH opcode
return validate(instructionSet)
}

func newShanghaiInstructionSet() JumpTable {
instructionSet := newMergeInstructionSet()
enable3855(&instructionSet) // PUSH0 instruction
Expand Down
3 changes: 3 additions & 0 deletions core/vm/opcodes.go
Expand Up @@ -100,6 +100,7 @@ const (
CHAINID OpCode = 0x46
SELFBALANCE OpCode = 0x47
BASEFEE OpCode = 0x48
BLOBHASH OpCode = 0x49
)

// 0x50 range - 'storage' and execution.
Expand Down Expand Up @@ -288,6 +289,7 @@ var opCodeToString = map[OpCode]string{
CHAINID: "CHAINID",
SELFBALANCE: "SELFBALANCE",
BASEFEE: "BASEFEE",
BLOBHASH: "BLOBHASH",

// 0x50 range - 'storage' and execution.
POP: "POP",
Expand Down Expand Up @@ -445,6 +447,7 @@ var stringToOp = map[string]OpCode{
"CALLDATACOPY": CALLDATACOPY,
"CHAINID": CHAINID,
"BASEFEE": BASEFEE,
"BLOBHASH": BLOBHASH,
"DELEGATECALL": DELEGATECALL,
"STATICCALL": STATICCALL,
"CODESIZE": CODESIZE,
Expand Down
5 changes: 3 additions & 2 deletions core/vm/runtime/env.go
Expand Up @@ -23,8 +23,9 @@ import (

func NewEnv(cfg *Config) *vm.EVM {
txContext := vm.TxContext{
Origin: cfg.Origin,
GasPrice: cfg.GasPrice,
Origin: cfg.Origin,
GasPrice: cfg.GasPrice,
BlobHashes: cfg.BlobHashes,
}
blockContext := vm.BlockContext{
CanTransfer: core.CanTransfer,
Expand Down
1 change: 1 addition & 0 deletions core/vm/runtime/runtime.go
Expand Up @@ -44,6 +44,7 @@ type Config struct {
Debug bool
EVMConfig vm.Config
BaseFee *big.Int
BlobHashes []common.Hash

State *state.StateDB
GetHashFn func(n uint64) common.Hash
Expand Down
9 changes: 9 additions & 0 deletions core/vm/testdata/precompiles/pointEvaluation.json
@@ -0,0 +1,9 @@
[
{
"Input": "013c03613f6fc558fb7e61e75602241ed9a2f04e36d8670aadd286e71b5ca9cc420000000000000000000000000000000000000000000000000000000000000031e5a2356cbc2ef6a733eae8d54bf48719ae3d990017ca787c419c7d369f8e3c83fac17c3f237fc51f90e2c660eb202a438bc2025baded5cd193c1a018c5885bc9281ba704d5566082e851235c7be763b2a99adff965e0a121ee972ebc472d02944a74f5c6243e14052e105124b70bf65faf85ad3a494325e269fad097842cba",
"Expected": "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001",
"Name": "pointEvaluation1",
"Gas": 50000,
"NoBenchmark": false
}
]
13 changes: 7 additions & 6 deletions params/protocol_params.go
Expand Up @@ -160,12 +160,13 @@ const (
RefundQuotient uint64 = 2
RefundQuotientEIP3529 uint64 = 5

BlobTxHashVersion = 0x01 // Version byte of the commitment hash
BlobTxMaxDataGasPerBlock = 1 << 19 // Maximum consumable data gas for data blobs per block
BlobTxTargetDataGasPerBlock = 1 << 18 // Target consumable data gas for data blobs per block (for 1559-like pricing)
BlobTxDataGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size)
BlobTxMinDataGasprice = 1 // Minimum gas price for data blobs
BlobTxDataGaspriceUpdateFraction = 2225652 // Controls the maximum rate of change for data gas price
BlobTxHashVersion = 0x01 // Version byte of the commitment hash
BlobTxMaxDataGasPerBlock = 1 << 19 // Maximum consumable data gas for data blobs per block
BlobTxTargetDataGasPerBlock = 1 << 18 // Target consumable data gas for data blobs per block (for 1559-like pricing)
BlobTxDataGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size)
BlobTxMinDataGasprice = 1 // Minimum gas price for data blobs
BlobTxDataGaspriceUpdateFraction = 2225652 // Controls the maximum rate of change for data gas price
BlobTxPointEvaluationPrecompileGas = 50000 // Gas price for the point evaluation precompile.
)

// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations
Expand Down