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, params: prototype withdrawal transactions #24468

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
core, params: prototype withdrawal transactions
  • Loading branch information
ralexstokes committed Mar 1, 2022
commit 96aae3d99707999142807411f959c2895007cf44
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