Skip to content

Commit

Permalink
Merge pull request #2 from lightclient/simple-blob
Browse files Browse the repository at this point in the history
Implement `DATAHASH` opcode and sharding fork
  • Loading branch information
protolambda committed Feb 20, 2022
2 parents 562723e + 89c1e27 commit 7a3a455
Show file tree
Hide file tree
Showing 23 changed files with 225 additions and 32 deletions.
1 change: 1 addition & 0 deletions accounts/abi/bind/backends/simulated.go
Expand Up @@ -812,6 +812,7 @@ func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data }
func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
func (m callMsg) DataHashes() []common.Hash { return m.CallMsg.DataHashes }

// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
Expand Down
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/transaction.go
Expand Up @@ -139,7 +139,7 @@ func Transaction(ctx *cli.Context) error {
r.Address = sender
}
// Check intrinsic gas
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil,
if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), len(tx.BlobVersionedHashes()), tx.To() == nil,
chainConfig.IsHomestead(new(big.Int)), chainConfig.IsIstanbul(new(big.Int))); err != nil {
r.Error = err
results = append(results, r)
Expand Down
2 changes: 1 addition & 1 deletion core/bench_test.go
Expand Up @@ -85,7 +85,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, nil, false, false, false)
gas, _ := IntrinsicGas(data, nil, 0, false, false, false)
signer := types.MakeSigner(gen.config, big.NewInt(int64(i)))
gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil {
Expand Down
85 changes: 85 additions & 0 deletions core/blockchain_test.go
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/protolambda/ztyp/view"
)

// So we can deterministically seed different blockchains
Expand Down Expand Up @@ -3704,3 +3705,87 @@ func TestEIP1559Transition(t *testing.T) {
t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual)
}
}

// TestDataBlobTxs tests the following:
//
// 1. Writes data hash from transaction to storage.
func TestDataBlobTxs(t *testing.T) {
var (
one = common.Hash{1}
two = common.Hash{2}
aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa")

// Generate a canonical chain to act as the main dataset
engine = ethash.NewFaker()
db = rawdb.NewMemoryDatabase()

// A sender who makes transactions, has some funds
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether))
gspec = &Genesis{
Config: params.AllEthashProtocolChanges,
Alloc: GenesisAlloc{
addr1: {Balance: funds},
// The address 0xAAAA writes dataHashes[1] to storage slot 0x0.
aa: {
Code: []byte{
byte(vm.PUSH1),
byte(0x1),
byte(vm.DATAHASH),
byte(vm.PUSH1),
byte(0x0),
byte(vm.SSTORE),
},
Nonce: 0,
Balance: big.NewInt(0),
},
},
}
)

gspec.Config.BerlinBlock = common.Big0
gspec.Config.LondonBlock = common.Big0
gspec.Config.ShardingForkBlock = common.Big0
genesis := gspec.MustCommit(db)
signer := types.LatestSigner(gspec.Config)

blocks, _ := GenerateChain(gspec.Config, genesis, engine, db, 1, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{1})
msg := types.BlobTxMessage{
Nonce: 0,
Gas: 500000,
}
msg.To.Address = (*types.AddressSSZ)(&aa)
msg.ChainID.SetFromBig((*big.Int)(gspec.Config.ChainID))
msg.Nonce = view.Uint64View(0)
msg.GasFeeCap.SetFromBig(newGwei(5))
msg.GasTipCap.SetFromBig(big.NewInt(2))
msg.BlobVersionedHashes = []common.Hash{one, two}
txdata := &types.SignedBlobTx{Message: msg}

tx := types.NewTx(txdata)
tx, _ = types.SignTx(tx, signer, key1)

b.AddTx(tx)
})

diskdb := rawdb.NewMemoryDatabase()
gspec.MustCommit(diskdb)

chain, err := NewBlockChain(diskdb, nil, gspec.Config, engine, vm.Config{}, nil, nil)
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
if n, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
}

state, _ := chain.State()

// 1. Check that the storage slot is set to dataHashes[1].
actual := state.GetState(aa, common.Hash{0})
if actual != two {
t.Fatalf("incorrect data hash written to state (want: %s, got: %s)", two, actual)
}
}
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()),
DataHashes: msg.DataHashes(),
}
}

Expand Down
6 changes: 4 additions & 2 deletions core/state_transition.go
Expand Up @@ -77,6 +77,7 @@ type Message interface {
IsFake() bool
Data() []byte
AccessList() types.AccessList
DataHashes() []common.Hash
}

// ExecutionResult includes all output after executing given evm
Expand Down Expand Up @@ -115,7 +116,7 @@ func (result *ExecutionResult) Revert() []byte {
}

// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, blobCount int, isContractCreation bool, isHomestead, isEIP2028 bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
Expand Down Expand Up @@ -152,6 +153,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation b
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
gas += uint64(accessList.StorageKeys()) * params.TxAccessListStorageKeyGas
}
gas += uint64(blobCount) * params.BlobGas
return gas, nil
}

Expand Down Expand Up @@ -295,7 +297,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
contractCreation := msg.To() == nil

// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, istanbul)
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), len(st.msg.DataHashes()), contractCreation, homestead, istanbul)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion core/tx_pool.go
Expand Up @@ -635,7 +635,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
return ErrInsufficientFunds
}
// Ensure the transaction has more gas than the basic tx fee.
intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul)
intrGas, err := IntrinsicGas(tx.Data(), tx.AccessList(), 0, tx.To() == nil, true, pool.istanbul)
if err != nil {
return err
}
Expand Down
7 changes: 4 additions & 3 deletions core/types/data_blob_tx.go
Expand Up @@ -4,13 +4,14 @@ import (
"encoding/hex"
"errors"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
"github.com/protolambda/ztyp/codec"
"github.com/protolambda/ztyp/conv"
"github.com/protolambda/ztyp/tree"
. "github.com/protolambda/ztyp/view"
"math/big"
)

type ECDSASignature struct {
Expand Down Expand Up @@ -432,6 +433,6 @@ func (stx *SignedBlobTx) rawSignatureValues() (v, r, s *big.Int) {
func (stx *SignedBlobTx) setSignatureValues(chainID, v, r, s *big.Int) {
(*uint256.Int)(&stx.Message.ChainID).SetFromBig(chainID)
stx.Signature.V = Uint8View(v.Uint64())
(*uint256.Int)(&stx.Signature.R).SetFromBig(chainID)
(*uint256.Int)(&stx.Signature.S).SetFromBig(chainID)
(*uint256.Int)(&stx.Signature.R).SetFromBig(r)
(*uint256.Int)(&stx.Signature.S).SetFromBig(s)
}
28 changes: 16 additions & 12 deletions core/types/transaction.go
Expand Up @@ -20,12 +20,13 @@ import (
"bytes"
"container/heap"
"errors"
"github.com/protolambda/ztyp/codec"
"io"
"math/big"
"sync/atomic"
"time"

"github.com/protolambda/ztyp/codec"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -596,6 +597,7 @@ type Message struct {
gasTipCap *big.Int
data []byte
accessList AccessList
dataHashes []common.Hash
isFake bool
}

Expand Down Expand Up @@ -627,6 +629,7 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
amount: tx.Value(),
data: tx.Data(),
accessList: tx.AccessList(),
dataHashes: tx.BlobVersionedHashes(),
isFake: false,
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
Expand All @@ -638,17 +641,18 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
return msg, err
}

func (m Message) From() common.Address { return m.from }
func (m Message) To() *common.Address { return m.to }
func (m Message) GasPrice() *big.Int { return m.gasPrice }
func (m Message) GasFeeCap() *big.Int { return m.gasFeeCap }
func (m Message) GasTipCap() *big.Int { return m.gasTipCap }
func (m Message) Value() *big.Int { return m.amount }
func (m Message) Gas() uint64 { return m.gasLimit }
func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data }
func (m Message) AccessList() AccessList { return m.accessList }
func (m Message) IsFake() bool { return m.isFake }
func (m Message) From() common.Address { return m.from }
func (m Message) To() *common.Address { return m.to }
func (m Message) GasPrice() *big.Int { return m.gasPrice }
func (m Message) GasFeeCap() *big.Int { return m.gasFeeCap }
func (m Message) GasTipCap() *big.Int { return m.gasTipCap }
func (m Message) Value() *big.Int { return m.amount }
func (m Message) Gas() uint64 { return m.gasLimit }
func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data }
func (m Message) AccessList() AccessList { return m.accessList }
func (m Message) DataHashes() []common.Hash { return m.dataHashes }
func (m Message) IsFake() bool { return m.isFake }

// copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address {
Expand Down
5 changes: 5 additions & 0 deletions core/types/transaction_signing.go
Expand Up @@ -40,6 +40,8 @@ type sigCache struct {
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
var signer Signer
switch {
case config.IsSharding(blockNumber):
signer = NewDankSigner(config.ChainID)
case config.IsLondon(blockNumber):
signer = NewLondonSigner(config.ChainID)
case config.IsBerlin(blockNumber):
Expand All @@ -63,6 +65,9 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
// have the current block number available, use MakeSigner instead.
func LatestSigner(config *params.ChainConfig) Signer {
if config.ChainID != nil {
if config.ShardingForkBlock != nil {
return NewDankSigner(config.ChainID)
}
if config.LondonBlock != nil {
return NewLondonSigner(config.ChainID)
}
Expand Down
23 changes: 23 additions & 0 deletions core/vm/eips.go
Expand Up @@ -174,3 +174,26 @@ func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
scope.Stack.push(baseFee)
return nil, nil
}

// enable3198 applies mini-danksharding (DATAHASH Opcode)
// - Adds an opcode that returns the versioned data hash of the tx at a index.
func enableSharding(jt *JumpTable) {
jt[DATAHASH] = &operation{
execute: opDataHash,
constantGas: GasFastestStep,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
}

// opDataHash implements DATAHASH opcode
func opDataHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
idx := scope.Stack.pop()
if uint64(len(interpreter.evm.TxContext.DataHashes)) < idx.Uint64() {
scope.Stack.push(uint256.NewInt(0))
} else {
hash := interpreter.evm.TxContext.DataHashes[idx.Uint64()]
scope.Stack.push(new(uint256.Int).SetBytes(hash.Bytes()))
}
return nil, nil
}
5 changes: 3 additions & 2 deletions core/vm/evm.go
Expand Up @@ -82,8 +82,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
DataHashes []common.Hash // Provides versioned data hashes for DATAHASH
}

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

func TestDataHash(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}},
} {
var (
env = NewEVM(BlockContext{}, TxContext{DataHashes: tt.hashes}, nil, params.TestChainConfig, Config{})
stack = newstack()
pc = uint64(0)
evmInterpreter = env.interpreter
)
stack.push(uint256.NewInt(tt.idx))
opDataHash(&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 @@ -69,6 +69,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
// If jump table was not initialised we set the default one.
if cfg.JumpTable == nil {
switch {
case evm.chainRules.IsSharding:
cfg.JumpTable = &shardingInstructionSet
case evm.chainRules.IsMerge:
cfg.JumpTable = &mergeInstructionSet
case evm.chainRules.IsLondon:
Expand Down
7 changes: 7 additions & 0 deletions core/vm/jump_table.go
Expand Up @@ -55,6 +55,7 @@ var (
berlinInstructionSet = newBerlinInstructionSet()
londonInstructionSet = newLondonInstructionSet()
mergeInstructionSet = newMergeInstructionSet()
shardingInstructionSet = newShardingInstructionSet()
)

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

func newShardingInstructionSet() JumpTable {
instructionSet := newLondonInstructionSet()
enableSharding(&instructionSet)
return validate(instructionSet)
}

func newMergeInstructionSet() JumpTable {
instructionSet := newLondonInstructionSet()
instructionSet[RANDOM] = &operation{
Expand Down

0 comments on commit 7a3a455

Please sign in to comment.