Skip to content

Commit

Permalink
Merge pull request #12 from quilt/aa-tx-execution
Browse files Browse the repository at this point in the history
Add Tests for Transaction Validation
  • Loading branch information
adietrichs authored Jun 9, 2020
2 parents f8e1997 + e6c57f1 commit 64ad2c4
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 22 deletions.
167 changes: 167 additions & 0 deletions core/aa_transaction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2015 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 core

import (
"encoding/binary"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
)

const (
contractCode = "3373ffffffffffffffffffffffffffffffffffffffff1460245736601f57005b600080fd5b60016000355b81900380602a57602035603957005b604035aa00"
)

// aaTransaction creates a new AA transaction to a deployed instance of the test contract.
// Parameters are the contract address, the transaction gas limit,
// the number of loops to run inside the contract (minimum: 1, gas per loop: 26),
// whether to call PAYGAS at the end, the gas price to be returned from PAYGAS (if called).
func aaTransaction(to common.Address, gaslimit uint64, loops uint64, callPaygas bool, gasPrice *big.Int) *types.Transaction {
data := make([]byte, 0x60)
if loops == 0 {
loops = 1
}
binary.BigEndian.PutUint64(data[0x18:0x20], loops)
if callPaygas {
data[0x3f] = 1
}
gasPriceBytes := gasPrice.Bytes()
copy(data[0x60-len(gasPriceBytes):], gasPriceBytes)

return types.NewTransaction(0, to, big.NewInt(0), gaslimit, big.NewInt(0), data).WithAASignature()
}

func setupBlockchain(blockGasLimit uint64) *BlockChain {
genesis := Genesis{Config: params.AllEthashProtocolChanges, GasLimit: blockGasLimit}
database := rawdb.NewMemoryDatabase()
genesis.MustCommit(database)
blockchain, _ := NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil)
return blockchain
}

func testValidate(blockchain *BlockChain, statedb *state.StateDB, transaction *types.Transaction, validationGasLimit uint64, expectedErr error, t *testing.T) {
var (
snapshotRevisionId = statedb.Snapshot()
context = NewEVMContext(types.AADummyMessage, blockchain.CurrentHeader(), blockchain, &common.Address{})
vmenv = vm.NewEVM(context, statedb, blockchain.Config(), vm.Config{PaygasMode: vm.PaygasHalt})
err = Validate(transaction, types.HomesteadSigner{}, vmenv, validationGasLimit)
)
if err != expectedErr {
t.Error("\n\texpected:", expectedErr, "\n\tgot:", err)
}
statedb.RevertToSnapshot(snapshotRevisionId)
}

func TestTransactionValidation(t *testing.T) {
var (
blockchain = setupBlockchain(10000000)
statedb, _ = blockchain.State()

key, _ = crypto.GenerateKey()
contractCreator = crypto.PubkeyToAddress(key.PublicKey)
contractAddress = crypto.CreateAddress(contractCreator, 0)
tx = &types.Transaction{}
)
statedb.SetBalance(contractAddress, big.NewInt(100000))
statedb.SetCode(contractAddress, common.FromHex(contractCode))

// test: invalid, no PAYGAS
tx = aaTransaction(contractAddress, 100000, 1, false, big.NewInt(1))
testValidate(blockchain, statedb, tx, 400000, ErrNoPaygas, t)

// test: invalid, insufficient funds
tx = aaTransaction(contractAddress, 100000, 1, true, big.NewInt(2))
testValidate(blockchain, statedb, tx, 400000, vm.ErrPaygasInsufficientFunds, t)

// test: invalid, gasLimit too low for intrinsic gas cost
tx = aaTransaction(contractAddress, 1, 1, true, big.NewInt(1))
testValidate(blockchain, statedb, tx, 400000, ErrIntrinsicGas, t)

// test: invalid, gasLimit too low for loops
tx = aaTransaction(contractAddress, 100000, 100000, true, big.NewInt(1))
testValidate(blockchain, statedb, tx, 400000, vm.ErrOutOfGas, t)

// test: valid, gasLimit < validationGasLimit
tx = aaTransaction(contractAddress, 100000, 1, true, big.NewInt(1))
testValidate(blockchain, statedb, tx, 400000, nil, t)

// test: valid, validationGasLimit < gasLimit
tx = aaTransaction(contractAddress, 100000, 1, true, big.NewInt(1))
testValidate(blockchain, statedb, tx, 50000, nil, t)

// test: invalid, validationGasLimit too low for intrinsic gas cost
tx = aaTransaction(contractAddress, 100000, 1, true, big.NewInt(1))
testValidate(blockchain, statedb, tx, 1, ErrIntrinsicGas, t)

// test: invalid, validationGasLimit too low for loops
tx = aaTransaction(contractAddress, 100000, 100000, true, big.NewInt(1))
testValidate(blockchain, statedb, tx, 50000, vm.ErrOutOfGas, t)

statedb.SetBalance(contractAddress, big.NewInt(0))

// test: invalid, insufficient funds
tx = aaTransaction(contractAddress, 100000, 1, true, big.NewInt(1))
testValidate(blockchain, statedb, tx, 400000, vm.ErrPaygasInsufficientFunds, t)

// test: valid, gasPrice 0
tx = aaTransaction(contractAddress, 100000, 1, true, big.NewInt(0))
testValidate(blockchain, statedb, tx, 400000, nil, t)
}

func TestInvalidEVMConfig(t *testing.T) {
var (
blockchain = setupBlockchain(10000000)
statedb, _ = blockchain.State()

context = NewEVMContext(types.AADummyMessage, blockchain.CurrentHeader(), blockchain, &common.Address{})
vmenv = vm.NewEVM(context, statedb, blockchain.Config(), vm.Config{})

tx = &types.Transaction{}
err = Validate(tx, types.HomesteadSigner{}, vmenv, 400000)
expectedErr = ErrIncorrectAAConfig
)
if err != expectedErr {
t.Error("\n\texpected:", expectedErr, "\n\tgot:", err)
}
}

func TestMalformedTransaction(t *testing.T) {
var (
blockchain = setupBlockchain(10000000)
statedb, _ = blockchain.State()

context = NewEVMContext(types.AADummyMessage, blockchain.CurrentHeader(), blockchain, &common.Address{})
vmenv = vm.NewEVM(context, statedb, blockchain.Config(), vm.Config{PaygasMode: vm.PaygasHalt})

key, _ = crypto.GenerateKey()
tx = pricedTransaction(0, 100000, big.NewInt(0), key)
err = Validate(tx, types.HomesteadSigner{}, vmenv, 400000)
expectedErr = ErrMalformedTransaction
)
if err != expectedErr {
t.Error("\n\texpected:", expectedErr, "\n\tgot:", err)
}
}
12 changes: 9 additions & 3 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,9 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if st.evm.PaygasMode() == vm.PaygasNoOp {
st.evm.SetPaygasMode(vm.PaygasContinue)
}
st.evm.SetPaygasLimit(st.msg.Gas())
if st.evm.PaygasLimit() == 0 {
st.evm.SetPaygasLimit(st.msg.Gas())
}
}

// Check clauses 1-3, buy gas if everything is correct
Expand Down Expand Up @@ -294,8 +296,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
}
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
if msg.IsAA() && st.evm.PaygasMode() == vm.PaygasContinue {
return nil, ErrNoPaygas
if msg.IsAA() && st.evm.PaygasMode() != vm.PaygasNoOp {
if vmerr != nil {
return nil, vmerr
} else {
return nil, ErrNoPaygas
}
}
}
if st.msg.IsAA() {
Expand Down
20 changes: 11 additions & 9 deletions core/transaction_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,30 @@ import (
)

var (
ErrIncorrectAAConfig = errors.New("incorrect AA config for EVM")
ErrIncorrectAAConfig = errors.New("incorrect AA config for EVM")
ErrMalformedTransaction = errors.New("AA transaction malformed")
)

func Validate(tx *types.Transaction, s types.Signer, evm *vm.EVM, gasLimit uint64) error {
if evm.PaygasMode() != vm.PaygasHalt || evm.PaygasPrice().Sign() != 0 {
if evm.PaygasMode() != vm.PaygasHalt {
return ErrIncorrectAAConfig
}
evm.SetPaygasLimit(tx.Gas())
if gasLimit > tx.Gas() {
gasLimit = tx.Gas()
}
msg, err := tx.AsMessage(s)
if err != nil {
return err
} else if !msg.IsAA() {
return ErrMalformedTransaction
}
if gasLimit > msg.Gas() {
gasLimit = msg.Gas()
}
msg.SetGas(gasLimit)
gp := new(GasPool).AddGas(gasLimit)
result, err := ApplyMessage(evm, msg, gp)
_, err = ApplyMessage(evm, msg, gp)
if err != nil {
return err
}
if result.Err != nil {
return result.Err
}
tx.SetAAGasPrice(evm.PaygasPrice())
return nil
}
27 changes: 18 additions & 9 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, e
return cpy, nil
}

func (tx *Transaction) WithAASignature() *Transaction {
cpy := &Transaction{data: tx.data}
cpy.data.V = big.NewInt(27)
return cpy
}

// Cost returns amount + gasprice * gaslimit.
func (tx *Transaction) Cost() *big.Int {
total := new(big.Int).Mul(tx.data.Price, new(big.Int).SetUint64(tx.data.GasLimit))
Expand Down Expand Up @@ -408,6 +414,8 @@ type Message struct {
isAA bool
}

var AADummyMessage = Message{from: common.NewEntryPointAddress(), gasPrice: big.NewInt(0)}

func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, checkNonce bool) Message {
isAA := from.IsEntryPoint()
return Message{
Expand All @@ -423,15 +431,16 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
}
}

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) 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) CheckNonce() bool { return m.checkNonce }
func (m Message) IsAA() bool { return m.isAA }
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) 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) CheckNonce() bool { return m.checkNonce }
func (m Message) IsAA() bool { return m.isAA }
func (m *Message) SetGas(gasLimit uint64) { m.gasLimit = gasLimit }
func (m Message) Sponsor() common.Address {
if !m.isAA {
return m.from
Expand Down
2 changes: 1 addition & 1 deletion core/vm/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ var (
ErrWriteProtection = errors.New("write protection")
ErrReturnDataOutOfBounds = errors.New("return data out of bounds")
ErrGasUintOverflow = errors.New("gas uint64 overflow")
ErrPaygasInsufficientFunds = errors.New("insufficient funds for gas * price + value")
ErrPaygasInsufficientFunds = errors.New("during paygas insufficient funds for gas * price + value")
)

// ErrStackUnderflow wraps an evm error when the items on the stack less
Expand Down
1 change: 1 addition & 0 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,5 +512,6 @@ func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }

func (evm *EVM) PaygasMode() PaygasMode { return evm.vmConfig.PaygasMode }
func (evm *EVM) SetPaygasMode(paygasMode PaygasMode) { evm.vmConfig.PaygasMode = paygasMode }
func (evm *EVM) PaygasLimit() uint64 { return evm.vmConfig.paygasLimit }
func (evm *EVM) SetPaygasLimit(paygasLimit uint64) { evm.vmConfig.paygasLimit = paygasLimit }
func (evm *EVM) PaygasPrice() *big.Int { return evm.vmConfig.paygasPrice }

0 comments on commit 64ad2c4

Please sign in to comment.