Skip to content

Commit

Permalink
core, params: prototype withdrawal transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
ralexstokes committed Mar 1, 2022
1 parent 687e4dc commit 96aae3d
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 5 deletions.
52 changes: 49 additions & 3 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package core

import (
"errors"
"fmt"

"github.com/ethereum/go-ethereum/consensus"
Expand Down Expand Up @@ -46,9 +47,53 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
return validator
}

func validateOnlyWithdrawalTransactions(txs []*types.Transaction) bool {
for _, tx := range txs {
if tx.Type() != types.WithdrawalTxType {
return false
}
}
return true
}

func validateNoWithdrawalTransactions(txs []*types.Transaction) bool {
for _, tx := range txs {
if tx.Type() == types.WithdrawalTxType {
return false
}
}
return true
}

// Return an error if the `block` fails EIP-4863 transaction ordering validation, or `nil` otherwise.
func validateCorrectTransactionOrdering(block *types.Block) error {
firstUserTx := -1
txs := block.Transactions()
for i, tx := range txs {
if tx.Type() != types.WithdrawalTxType {
firstUserTx = i
break
}
}
if firstUserTx == -1 {
return nil
} else {
hasCorrectWithdrawalTx := validateOnlyWithdrawalTransactions(txs[:firstUserTx])
if !hasCorrectWithdrawalTx {
return errors.New("has user transactions where only withdrawal transactions should be in block body")
}
hasCorrectUserTx := validateNoWithdrawalTransactions(txs[firstUserTx:])
if !hasCorrectUserTx {
return errors.New("has withdrawal transactions where only user transactions should be in block body")
}
}

return nil
}

// ValidateBody validates the given block's uncles and verifies the block
// header's transaction and uncle roots. The headers are assumed to be already
// validated at this point.
// header's transaction and uncle roots, along with transaction ordering.
// The headers are assumed to be already validated at this point.
func (v *BlockValidator) ValidateBody(block *types.Block) error {
// Check whether the block's known, and if not, that it's linkable
if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) {
Expand All @@ -71,7 +116,8 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
}
return consensus.ErrPrunedAncestor
}
return nil
// Ensure any withdrawal transactions are before any other type of transaction.
return validateCorrectTransactionOrdering(block)
}

// ValidateState validates the various changes that happen after a state
Expand Down
21 changes: 20 additions & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,32 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return receipts, allLogs, *usedGas, nil
}

func applyWithdrawalTx(tx *types.Transaction, statedb *state.StateDB) (*ExecutionResult, error) {
statedb.AddBalance(*tx.To(), tx.Value())

// TODO emit logs?
// statedb.AddLog(...)

return &ExecutionResult{
UsedGas: 0,
Err: nil,
ReturnData: nil,
}, nil
}

func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
// Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb)

// Apply the transaction to the current state (included in the env).
result, err := ApplyMessage(evm, msg, gp)
var result *ExecutionResult
var err error
if config.IsShanghai(blockNumber) && tx.Type() == types.WithdrawalTxType {
result, err = applyWithdrawalTx(tx, statedb)
} else {
result, err = ApplyMessage(evm, msg, gp)
}
if err != nil {
return nil, err
}
Expand Down
44 changes: 43 additions & 1 deletion core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package types
import (
"bytes"
"container/heap"
"encoding/binary"
"errors"
"io"
"math/big"
Expand All @@ -45,6 +46,7 @@ const (
LegacyTxType = iota
AccessListTxType
DynamicFeeTxType
WithdrawalTxType
)

// Transaction is an Ethereum transaction.
Expand Down Expand Up @@ -102,10 +104,27 @@ func (tx *Transaction) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, buf.Bytes())
}

func encodeWithdrawalTxSSZ(w *bytes.Buffer, tx TxData) error {
to := tx.to()
_, err := w.Write(to[:])
if err != nil {
return err
}

value := tx.value()
valueBuf := make([]byte, 32)
value.FillBytes(valueBuf)
return binary.Write(w, binary.LittleEndian, valueBuf)
}

// encodeTyped writes the canonical encoding of a typed transaction to w.
func (tx *Transaction) encodeTyped(w *bytes.Buffer) error {
w.WriteByte(tx.Type())
return rlp.Encode(w, tx.inner)
if tx.Type() == WithdrawalTxType {
return encodeWithdrawalTxSSZ(w, tx.inner)
} else {
return rlp.Encode(w, tx.inner)
}
}

// MarshalBinary returns the canonical encoding of the transaction.
Expand Down Expand Up @@ -172,6 +191,25 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error {
return nil
}

func decodeWithdrawalTxSSZ(b []byte, tx *WithdrawalTx) error {
if len(b) != 52 {
return errors.New("withdrawal tx data is incorrectly sized")
}

to := common.BytesToAddress(b[:20])
tx.To = &to

buf := bytes.NewReader(b[20:])
valueBuf := make([]byte, 32)
err := binary.Read(buf, binary.LittleEndian, &valueBuf)
if err != nil {
return err
}

tx.Value = new(big.Int).SetBytes(valueBuf)
return nil
}

// decodeTyped decodes a typed transaction from the canonical format.
func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
if len(b) == 0 {
Expand All @@ -186,6 +224,10 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
var inner DynamicFeeTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case WithdrawalTxType:
var inner WithdrawalTx
err := decodeWithdrawalTxSSZ(b[1:], &inner)
return &inner, err
default:
return nil, ErrTxTypeNotSupported
}
Expand Down
14 changes: 14 additions & 0 deletions core/types/transaction_marshalling.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
enc.V = (*hexutil.Big)(tx.V)
enc.R = (*hexutil.Big)(tx.R)
enc.S = (*hexutil.Big)(tx.S)
case *WithdrawalTx:
enc.To = t.To()
enc.Value = (*hexutil.Big)(tx.Value)
}
return json.Marshal(&enc)
}
Expand Down Expand Up @@ -263,6 +266,17 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
}
}

case WithdrawalTxType:
var itx WithdrawalTx
inner = &itx
if dec.To == nil {
return errors.New("missing required field 'to' in transaction")
}
itx.To = dec.To
if dec.Value == nil {
return errors.New("missing required field 'value' in transaction")
}
itx.Value = (*big.Int)(dec.Value)
default:
return ErrTxTypeNotSupported
}
Expand Down
68 changes: 68 additions & 0 deletions core/types/withdrawal_tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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 types

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
)

// `WithdrawalTx` mirrors a withdrawal receipt committed to
// on the consensus layer.
// This receipt is guaranteed to have some execution address
// and an amount in Gwei.
// NOTE: the amount is converted to Wei when the transaction data is
// unmarshalled into this struct.
type WithdrawalTx struct {
To *common.Address
Value *big.Int
}

// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *WithdrawalTx) copy() TxData {
cpy := &WithdrawalTx{
To: copyAddressPtr(tx.To),
Value: new(big.Int),
}
if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
return cpy
}

// accessors for innerTx.
func (tx *WithdrawalTx) txType() byte { return WithdrawalTxType }
func (tx *WithdrawalTx) chainID() *big.Int { return new(big.Int) }
func (tx *WithdrawalTx) accessList() AccessList { return nil }
func (tx *WithdrawalTx) data() []byte { return nil }
func (tx *WithdrawalTx) gas() uint64 { return 0 }
func (tx *WithdrawalTx) gasFeeCap() *big.Int { return new(big.Int) }
func (tx *WithdrawalTx) gasTipCap() *big.Int { return new(big.Int) }
func (tx *WithdrawalTx) gasPrice() *big.Int { return new(big.Int) }
func (tx *WithdrawalTx) value() *big.Int { return tx.Value }
func (tx *WithdrawalTx) nonce() uint64 { return 0 }
func (tx *WithdrawalTx) to() *common.Address { return tx.To }

func (tx *WithdrawalTx) rawSignatureValues() (v, r, s *big.Int) {
// return "zero" values, this method should not be called
return new(big.Int), new(big.Int), new(big.Int)
}

func (tx *WithdrawalTx) setSignatureValues(chainID, v, r, s *big.Int) {
// no-op, keep for broader compatibility
}
6 changes: 6 additions & 0 deletions params/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ type ChainConfig struct {
LondonBlock *big.Int `json:"londonBlock,omitempty"` // London switch block (nil = no fork, 0 = already on london)
ArrowGlacierBlock *big.Int `json:"arrowGlacierBlock,omitempty"` // Eip-4345 (bomb delay) switch block (nil = no fork, 0 = already activated)
MergeForkBlock *big.Int `json:"mergeForkBlock,omitempty"` // EIP-3675 (TheMerge) switch block (nil = no fork, 0 = already in merge proceedings)
ShanghaiBlock *big.Int `json:"shanghaiForkBlock,omitempty"` // Shanghai switch block (nil = no fork, 0 = already on shanghai)

// TerminalTotalDifficulty is the amount of total difficulty reached by
// the network that triggers the consensus upgrade.
Expand Down Expand Up @@ -475,6 +476,11 @@ func (c *ChainConfig) IsArrowGlacier(num *big.Int) bool {
return isForked(c.ArrowGlacierBlock, num)
}

// IsShanghai returns whether num is either equal to the Shanghai (EIP-TBD) fork block or greater.
func (c *ChainConfig) IsShanghai(num *big.Int) bool {
return isForked(c.ShanghaiBlock, num)
}

// IsTerminalPoWBlock returns whether the given block is the last block of PoW stage.
func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *big.Int) bool {
if c.TerminalTotalDifficulty == nil {
Expand Down

0 comments on commit 96aae3d

Please sign in to comment.