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

Feature/account abstraction #495

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
Open
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
60 changes: 40 additions & 20 deletions ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,7 @@ func (diff *StateOverride) Apply(state *state.StateDB) error {
return nil
}

func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*evmcore.ExecutionResult, error) {
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, forceTransactionFeePaid bool) (*evmcore.ExecutionResult, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())

state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
Expand Down Expand Up @@ -989,6 +989,9 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash

// Execute the message.
gp := new(evmcore.GasPool).AddGas(math.MaxUint64)
if forceTransactionFeePaid {
evm.TransactionFeePaid = true
}
result, err := evmcore.ApplyMessage(evm, msg, gp)
if err := vmError(); err != nil {
return nil, err
Expand Down Expand Up @@ -1041,7 +1044,7 @@ func (e *revertError) ErrorData() interface{} {
// Note, this function doesn't make and changes in the state/blockchain and is
// useful to execute and retrieve values.
func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), true)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1087,7 +1090,12 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
if state == nil || err != nil {
return 0, err
}
balance := state.GetBalance(*args.From) // from can't be nil
var balance *big.Int
if args.From.IsEntryPoint() {
balance = state.GetBalance(*args.To) // to can't be nil
} else {
balance = state.GetBalance(*args.From) // from can't be nil
}
available := new(big.Int).Set(balance)
if args.Value != nil {
if args.Value.ToInt().Cmp(available) >= 0 {
Expand Down Expand Up @@ -1119,7 +1127,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
executable := func(gas uint64) (bool, *evmcore.ExecutionResult, error) {
args.Gas = (*hexutil.Uint64)(&gas)

result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap)
result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap, false)
if err != nil {
if errors.Is(err, evmcore.ErrIntrinsicGas) {
return true, nil, nil // Special case, raise gas limit
Expand Down Expand Up @@ -1779,29 +1787,41 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args TransactionArgs) (common.Hash, error) {
// Look up the wallet containing the requested signer
account := accounts.Account{Address: args.from()}

wallet, err := s.b.AccountManager().Find(account)
if err != nil {
return common.Hash{}, err
isAATransaction := args.From.IsEntryPoint()
var (
signed *types.Transaction
wallet accounts.Wallet
err error
)
if !isAATransaction {
wallet, err = s.b.AccountManager().Find(account)
if err != nil {
return common.Hash{}, err
}
}

if args.Nonce == nil {
// Hold the addresse's mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts.
s.nonceLock.LockAddr(args.from())
defer s.nonceLock.UnlockAddr(args.from())
}

// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}
// Assemble the transaction and sign with the wallet
tx := args.toTransaction()

signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
if err != nil {
return common.Hash{}, err
if isAATransaction {
if err = args.setAADefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}
tx := args.toTransaction()
signed = tx.WithAASignature()
} else {
// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}
// Assemble the transaction and sign with the wallet
tx := args.toTransaction()
signed, err = wallet.SignTx(account, tx, s.b.ChainConfig().ChainID)
if err != nil {
return common.Hash{}, err
}
}
return SubmitTransaction(ctx, s.b, signed)
}
Expand Down
68 changes: 67 additions & 1 deletion ethapi/transaction_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
Value: args.Value,
Data: args.Data,
AccessList: args.AccessList,
Nonce: args.Nonce,
}
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap())
Expand All @@ -162,6 +163,58 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
return nil
}

// setDefaults is a helper function that fills in default values for unspecified AA tx fields.
func (args *TransactionArgs) setAADefaults(ctx context.Context, b Backend) error {
if args.GasPrice == nil {
price := b.SuggestGasTipCap(ctx, gasprice.AsDefaultCertainty)
price.Add(price, b.MinGasPrice())
args.GasPrice = (*hexutil.Big)(price)
}
if args.Value == nil {
args.Value = new(hexutil.Big)
} else if args.Value.ToInt().Sign() != 0 {
return errors.New(`"value" has to be 0 for account abstraction transactions`)
}
if args.To == nil {
return errors.New(`AA transactions require "to" address`)
}
if args.Nonce == nil {
nonce, err := b.GetPoolNonce(ctx, *args.To)
if err != nil {
return err
}
args.Nonce = (*hexutil.Uint64)(&nonce)
}
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
return errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
}
// Estimate the gas usage if necessary.
if args.Gas == nil {
// For backwards-compatibility reason, we try both input and data
// but input is preferred.
input := args.Input
if input == nil {
input = args.Data
}
callArgs := TransactionArgs{
From: args.From, // From shouldn't be nil
To: args.To,
GasPrice: args.GasPrice,
Value: args.Value,
Nonce: args.Nonce,
Data: input,
}
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap())
if err != nil {
return err
}
args.Gas = &estimated
log.Trace("Estimate gas usage automatically", "gas", args.Gas)
}
return nil
}

// ToMessage converts the transaction arguments to the Message type used by the
// core evm. This method is used in calls and traces that do not require a real
// live transaction.
Expand Down Expand Up @@ -229,7 +282,11 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (t
if args.AccessList != nil {
accessList = *args.AccessList
}
msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, gasFeeCap, gasTipCap, data, accessList, true)
nonce := uint64(0)
if args.Nonce != nil {
nonce = uint64(*args.Nonce)
}
msg := types.NewMessage(addr, args.To, nonce, value, gas, gasPrice, gasFeeCap, gasTipCap, data, accessList, true)
return msg, nil
}

Expand All @@ -238,6 +295,15 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (t
func (args *TransactionArgs) toTransaction() *types.Transaction {
var data types.TxData
switch {
case args.From.IsEntryPoint():
data = &types.AccountAbstractionTx{
To: args.To,
Nonce: uint64(*args.Nonce),
Gas: uint64(*args.Gas),
GasPrice: (*big.Int)(args.GasPrice),
Value: (*big.Int)(args.Value),
Data: args.data(),
}
case args.MaxFeePerGas != nil:
al := types.AccessList{}
if args.AccessList != nil {
Expand Down
2 changes: 1 addition & 1 deletion eventcheck/basiccheck/basic_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func validateTx(tx *types.Transaction) error {
return ErrNegativeValue
}
// Ensure the transaction has more gas than the basic tx fee.
intrGas, err := evmcore.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil)
intrGas, err := evmcore.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, tx.Type() == types.AccountAbstractionTxType)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion eventcheck/epochcheck/epoch_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func CheckTxs(txs types.Transactions, rules opera.Rules) error {
maxType = 1
}
if rules.Upgrades.London {
maxType = 2
maxType = 3
}
for _, tx := range txs {
if tx.Type() > maxType {
Expand Down
2 changes: 1 addition & 1 deletion evmcore/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
return func(i int, gen *BlockGen) {
toaddr := common.Address{}
data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, types.AccessList{}, false)
gas, _ := IntrinsicGas(data, types.AccessList{}, false, false)
tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data), types.HomesteadSigner{}, benchRootKey)
gen.AddTx(tx)
}
Expand Down
9 changes: 9 additions & 0 deletions evmcore/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,13 @@ var (

// ErrSenderNoEOA is returned if the sender of a transaction is a contract.
ErrSenderNoEOA = errors.New("sender not an eoa")

// ErrSenderNoContract is returned if the sender of a transaction is not a contract.
ErrSenderNoContract = errors.New("sender not a contract")

// ErrNoRecipient is returned if a recipient is not specified
ErrNoRecipient = errors.New("no recipient specified for account abstraction transaction")

// ErrMalformedAATransaction is returned if an AA transaction is malformed
ErrMalformedAATransaction = errors.New("AA transaction malformed")
)
15 changes: 13 additions & 2 deletions evmcore/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
)

Expand Down Expand Up @@ -60,9 +61,19 @@ func NewEVMBlockContext(header *EvmHeader, chain DummyChain, author *common.Addr

// NewEVMTxContext creates a new transaction context for a single transaction.
func NewEVMTxContext(msg Message) vm.TxContext {
var origin common.Address
if msg.IsAA() {
origin = types.AAEntryPoint
} else {
origin = msg.From()
}
return vm.TxContext{
Origin: msg.From(),
GasPrice: new(big.Int).Set(msg.GasPrice()),
Origin: origin,
GasPrice: new(big.Int).Set(msg.GasPrice()),
GasLimit: msg.Gas(),
Nonce: msg.Nonce(),
TransactionFeePaid: !msg.IsAA(),
AccountAbstraction: msg.IsAA(),
}
}

Expand Down
Loading