WIP: manually bring over RIP-7560 related code; compiles but does not run

This commit is contained in:
Alex Forshtat 2024-04-29 17:29:07 +02:00 committed by Dror Tirosh
parent 4bdbaab471
commit 9da00cd8c0
15 changed files with 1358 additions and 1 deletions

View file

@ -0,0 +1,653 @@
package core
import (
"encoding/binary"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"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/params"
"github.com/holiman/uint256"
"math/big"
"strings"
)
type ValidationPhaseResult struct {
TxIndex int
Tx *types.Transaction
TxHash common.Hash
PaymasterContext []byte
DeploymentUsedGas uint64
ValidationUsedGas uint64
PmValidationUsedGas uint64
SenderValidAfter uint64
SenderValidUntil uint64
PmValidAfter uint64
PmValidUntil uint64
IsEOA bool
}
// HandleRip7560Transactions apply state changes of all sequential RIP-7560 transactions and return
// the number of handled transactions
// the transactions array must start with the RIP-7560 transaction
func HandleRip7560Transactions(transactions []*types.Transaction, index int, statedb *state.StateDB, coinbase *common.Address, header *types.Header, gp *GasPool, chainConfig *params.ChainConfig, bc ChainContext, cfg vm.Config) ([]*types.Transaction, types.Receipts, []*types.Log, error) {
validatedTransactions := make([]*types.Transaction, 0)
receipts := make([]*types.Receipt, 0)
allLogs := make([]*types.Log, 0)
i := index
for {
if i >= len(transactions) {
break
}
if transactions[i].Type() != types.Rip7560Type {
break
}
iTransactions, iReceipts, iLogs, err := handleRip7560Transactions(transactions, index, statedb, coinbase, header, gp, chainConfig, bc, cfg)
if err != nil {
return nil, nil, nil, err
}
validatedTransactions = append(validatedTransactions, iTransactions...)
receipts = append(receipts, iReceipts...)
allLogs = append(allLogs, iLogs...)
}
return validatedTransactions, receipts, allLogs, nil
}
func handleRip7560Transactions(transactions []*types.Transaction, index int, statedb *state.StateDB, coinbase *common.Address, header *types.Header, gp *GasPool, chainConfig *params.ChainConfig, bc ChainContext, cfg vm.Config) ([]*types.Transaction, types.Receipts, []*types.Log, error) {
validationPhaseResults := make([]*ValidationPhaseResult, 0)
validatedTransactions := make([]*types.Transaction, 0)
receipts := make([]*types.Receipt, 0)
allLogs := make([]*types.Log, 0)
signer := types.MakeSigner(chainConfig, header.Number, header.Time)
for i, tx := range transactions[index:] {
if tx.Type() != types.Rip7560Type {
break
}
aatx := tx.Rip7560TransactionData()
isEoa, err := isTransactionEOA(tx, statedb, signer)
if err != nil {
return nil, nil, nil, err
}
statedb.SetTxContext(tx.Hash(), index+i)
err = BuyGasRip7560Transaction(aatx, statedb)
var vpr *ValidationPhaseResult
if isEoa {
blockContext := NewEVMBlockContext(header, bc, coinbase)
tmpMsg, err := TransactionToMessage(tx, signer, header.BaseFee)
txContext := NewEVMTxContext(tmpMsg)
evm := vm.NewEVM(blockContext, txContext, statedb, chainConfig, cfg)
//signer := types.MakeSigner(chainConfig, header.Number, header.Time)
signingHash := signer.Hash(tx)
vpr, err = validateRip7560TransactionFromEOA(tx, signingHash, statedb, evm, coinbase, header, gp, chainConfig)
if err != nil {
return nil, nil, nil, err
}
} else {
if err != nil {
return nil, nil, nil, err
}
vpr, err = ApplyRip7560ValidationPhases(chainConfig, bc, coinbase, gp, statedb, header, tx, cfg)
if err != nil {
return nil, nil, nil, err
}
}
validationPhaseResults = append(validationPhaseResults, vpr)
validatedTransactions = append(validatedTransactions, tx)
}
for i, vpr := range validationPhaseResults {
// TODO: this will miss all validation phase events - pass in 'vpr'
statedb.SetTxContext(vpr.Tx.Hash(), i)
receipt, err := ApplyRip7560ExecutionPhase(chainConfig, vpr, bc, coinbase, gp, statedb, header, cfg)
if err != nil {
return nil, nil, nil, err
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
}
return validatedTransactions, receipts, allLogs, nil
}
// BuyGasRip7560Transaction
// todo: move to a suitable interface, whatever that is
// todo 2: maybe handle the "shared gas pool" situation instead of just overriding it completely?
func BuyGasRip7560Transaction(st *types.Rip7560AccountAbstractionTx, state vm.StateDB) error {
gasLimit := st.Gas + st.ValidationGas + st.PaymasterGas + st.PostOpGas
mgval := new(uint256.Int).SetUint64(gasLimit)
gasFeeCap, _ := uint256.FromBig(st.GasFeeCap)
mgval = mgval.Mul(mgval, gasFeeCap)
balanceCheck := new(uint256.Int).Set(mgval)
chargeFrom := *st.Sender
if len(st.PaymasterData) >= 20 {
chargeFrom = [20]byte(st.PaymasterData[:20])
}
if have, want := state.GetBalance(chargeFrom), balanceCheck; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, chargeFrom.Hex(), have, want)
}
state.SubBalance(chargeFrom, mgval, 0)
return nil
}
// TODO: not needed with subtype - only use to validate transaction, maybe
func isTransactionEOA(tx *types.Transaction, statedb *state.StateDB, signer types.Signer) (bool, error) {
aatx := tx.Rip7560TransactionData()
senderHasCode := statedb.GetCodeSize(*aatx.Sender) != 0 || len(aatx.DeployerData) != 0
if senderHasCode {
return false, nil
}
address, err := signer.Sender(tx)
if err != nil {
return false, err
}
if address.Cmp(*tx.Rip7560TransactionData().Sender) != 0 {
return false, errors.New("recovered signature does not match the claimed EOA sender")
}
return true, nil
}
func validateRip7560TransactionFromEOA(tx *types.Transaction, signingHash common.Hash, statedb *state.StateDB, evm *vm.EVM, coinbase *common.Address, header *types.Header, gp *GasPool, chainConfig *params.ChainConfig) (*ValidationPhaseResult, error) {
// TODO: paymaste is actually optional for eoa-type-4 -> check paymaster data len()
paymasterContext, pmValidationUsedGas, pmValidAfter, pmValidUntil, err := applyPaymasterValidationFrame(tx, chainConfig, signingHash, evm, gp, statedb, header)
if err != nil {
return nil, err
}
err = validateValidityTimeRange(header.Time, pmValidAfter, pmValidUntil)
if err != nil {
return nil, err
}
vpr := &ValidationPhaseResult{
Tx: tx,
TxHash: tx.Hash(),
PaymasterContext: paymasterContext,
DeploymentUsedGas: 0,
ValidationUsedGas: 0,
PmValidationUsedGas: pmValidationUsedGas,
SenderValidAfter: 0,
SenderValidUntil: 0,
PmValidAfter: pmValidAfter,
PmValidUntil: pmValidUntil,
IsEOA: true,
}
return vpr, nil
}
func ApplyRip7560FrameMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
return NewRip7560StateTransition(evm, msg, gp).TransitionDb()
}
// NewRip7560StateTransition initialises and returns a new state transition object.
func NewRip7560StateTransition(evm *vm.EVM, msg *Message, gp *GasPool) *StateTransition {
return &StateTransition{
gp: gp,
evm: evm,
msg: msg,
state: evm.StateDB,
rip7560Frame: true,
}
}
// GetRip7560AccountNonce reads the two-dimensional RIP-7560 nonce from the given blockchain state
func GetRip7560AccountNonce(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config, sender common.Address, nonceKey *big.Int) uint64 {
// todo: this is a copy paste of 5 lines that need 8 parameters to run, wtf?
blockContext := NewEVMBlockContext(header, bc, author)
message, err := TransactionToMessage(tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee)
txContext := NewEVMTxContext(message)
vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg)
vmenv.Reset(txContext, statedb) // TODO what does this 'reset' do?
from := common.HexToAddress("0x0000000000000000000000000000000000000000")
// todo: read NM address from global config
nonceManager := common.HexToAddress("0xdebc121d1b09bc03ff57fa1f96514d04a1f0f59d")
fromBigNonceKey256, _ := uint256.FromBig(nonceKey)
key := make([]byte, 24)
fromBigNonceKey256.WriteToSlice(key)
nonceManagerData := make([]byte, 0)
nonceManagerData = append(nonceManagerData[:], sender.Bytes()...)
nonceManagerData = append(nonceManagerData[:], key...)
nonceManagerMsg := &Message{
From: from,
To: &nonceManager,
Value: big.NewInt(0),
GasLimit: 100000,
GasPrice: big.NewInt(875000000),
GasFeeCap: big.NewInt(875000000),
GasTipCap: big.NewInt(875000000),
Data: nonceManagerData,
AccessList: make(types.AccessList, 0),
SkipAccountChecks: true,
IsRip7560Frame: true,
}
resultNonceManager, err := ApplyRip7560FrameMessage(vmenv, nonceManagerMsg, gp)
if err != nil {
// todo: handle
return 777
}
if resultNonceManager.Err != nil {
return 888
}
if resultNonceManager.ReturnData == nil {
return 999
}
return big.NewInt(0).SetBytes(resultNonceManager.ReturnData).Uint64()
}
func ApplyRip7560ValidationPhases(chainConfig *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config) (*ValidationPhaseResult, error) {
/*** Nonce Manger Frame ***/
nonceManagerMsg := prepareNonceManagerMessage(tx, chainConfig)
blockContext := NewEVMBlockContext(header, bc, author)
txContext := NewEVMTxContext(nonceManagerMsg)
txContext.Origin = *tx.Rip7560TransactionData().Sender
evm := vm.NewEVM(blockContext, txContext, statedb, chainConfig, cfg)
resultNonceManager, err := ApplyRip7560FrameMessage(evm, nonceManagerMsg, gp)
if err != nil {
return nil, err
}
statedb.IntermediateRoot(true)
if resultNonceManager.Err != nil {
return nil, resultNonceManager.Err
}
/*** Deployer Frame ***/
deployerMsg := prepareDeployerMessage(tx, chainConfig)
var deploymentUsedGas uint64
if deployerMsg != nil {
resultDeployer, err := ApplyRip7560FrameMessage(evm, deployerMsg, gp)
if err != nil {
return nil, err
}
statedb.IntermediateRoot(true)
if resultDeployer.Failed() {
// TODO: bubble up the inner error message to the user, if possible
return nil, errors.New("account deployment failed - invalid transaction")
}
deploymentUsedGas = resultDeployer.UsedGas
}
/*** Account Validation Frame ***/
signer := types.MakeSigner(chainConfig, header.Number, header.Time)
signingHash := signer.Hash(tx)
accountValidationMsg, err := prepareAccountValidationMessage(tx, chainConfig, signingHash, deploymentUsedGas)
resultAccountValidation, err := ApplyRip7560FrameMessage(evm, accountValidationMsg, gp)
if err != nil {
return nil, err
}
statedb.IntermediateRoot(true)
if resultAccountValidation.Err != nil {
return nil, resultAccountValidation.Err
}
validAfter, validUntil, err := validateAccountReturnData(resultAccountValidation.ReturnData)
if err != nil {
return nil, err
}
err = validateValidityTimeRange(header.Time, validAfter, validUntil)
if err != nil {
return nil, err
}
paymasterContext, pmValidationUsedGas, pmValidAfter, pmValidUntil, err := applyPaymasterValidationFrame(tx, chainConfig, signingHash, evm, gp, statedb, header)
vpr := &ValidationPhaseResult{
Tx: tx,
TxHash: tx.Hash(),
PaymasterContext: paymasterContext,
DeploymentUsedGas: deploymentUsedGas,
ValidationUsedGas: resultAccountValidation.UsedGas,
PmValidationUsedGas: pmValidationUsedGas,
SenderValidAfter: validAfter,
SenderValidUntil: validUntil,
PmValidAfter: pmValidAfter,
PmValidUntil: pmValidUntil,
IsEOA: false,
}
return vpr, nil
}
func applyPaymasterValidationFrame(tx *types.Transaction, chainConfig *params.ChainConfig, signingHash common.Hash, evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header) ([]byte, uint64, uint64, uint64, error) {
/*** Paymaster Validation Frame ***/
var pmValidationUsedGas uint64
var paymasterContext []byte
var pmValidAfter uint64
var pmValidUntil uint64
paymasterMsg, err := preparePaymasterValidationMessage(tx, chainConfig, signingHash)
if err != nil {
return nil, 0, 0, 0, err
}
if paymasterMsg != nil {
resultPm, err := ApplyRip7560FrameMessage(evm, paymasterMsg, gp)
if err != nil {
return nil, 0, 0, 0, err
}
statedb.IntermediateRoot(true)
if resultPm.Failed() {
return nil, 0, 0, 0, errors.New("paymaster validation failed - invalid transaction")
}
pmValidationUsedGas = resultPm.UsedGas
paymasterContext, pmValidAfter, pmValidUntil, err = validatePaymasterReturnData(resultPm.ReturnData)
if err != nil {
return nil, 0, 0, 0, err
}
err = validateValidityTimeRange(header.Time, pmValidAfter, pmValidUntil)
if err != nil {
return nil, 0, 0, 0, err
}
}
return paymasterContext, pmValidationUsedGas, pmValidAfter, pmValidUntil, nil
}
func applyPaymasterPostOpFrame(vpr *ValidationPhaseResult, executionResult *ExecutionResult, evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header) (*ExecutionResult, error) {
var paymasterPostOpResult *ExecutionResult
paymasterPostOpMsg, err := preparePostOpMessage(vpr, evm.ChainConfig(), executionResult)
if err != nil {
return nil, err
}
paymasterPostOpResult, err = ApplyRip7560FrameMessage(evm, paymasterPostOpMsg, gp)
if err != nil {
return nil, err
}
// TODO: revert the execution phase changes
return paymasterPostOpResult, nil
}
func ApplyRip7560ExecutionPhase(config *params.ChainConfig, vpr *ValidationPhaseResult, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, cfg vm.Config) (*types.Receipt, error) {
// TODO: snapshot EVM - we will revert back here if postOp fails
blockContext := NewEVMBlockContext(header, bc, author)
message, err := TransactionToMessage(vpr.Tx, types.MakeSigner(config, header.Number, header.Time), header.BaseFee)
txContext := NewEVMTxContext(message)
txContext.Origin = *vpr.Tx.Rip7560TransactionData().Sender
evm := vm.NewEVM(blockContext, txContext, statedb, config, cfg)
var accountExecutionMsg *Message
if vpr.IsEOA {
accountExecutionMsg, err = prepareEOATargetExecutionMessage(vpr.Tx)
if err != nil {
return nil, err
}
} else {
accountExecutionMsg = prepareAccountExecutionMessage(vpr.Tx, evm.ChainConfig())
}
executionResult, err := ApplyRip7560FrameMessage(evm, accountExecutionMsg, gp)
if err != nil {
return nil, err
}
root := statedb.IntermediateRoot(true).Bytes()
var paymasterPostOpResult *ExecutionResult
if len(vpr.PaymasterContext) != 0 {
paymasterPostOpResult, err = applyPaymasterPostOpFrame(vpr, executionResult, evm, gp, statedb, header)
root = statedb.IntermediateRoot(true).Bytes()
}
if err != nil {
return nil, err
}
cumulativeGasUsed :=
vpr.ValidationUsedGas +
vpr.DeploymentUsedGas +
vpr.PmValidationUsedGas +
executionResult.UsedGas
if paymasterPostOpResult != nil {
cumulativeGasUsed +=
paymasterPostOpResult.UsedGas
}
receipt := &types.Receipt{Type: vpr.Tx.Type(), PostState: root, CumulativeGasUsed: cumulativeGasUsed}
// Set the receipt logs and create the bloom filter.
receipt.Logs = statedb.GetLogs(vpr.Tx.Hash(), header.Number.Uint64(), header.Hash())
if executionResult.Failed() || (paymasterPostOpResult != nil && paymasterPostOpResult.Failed()) {
receipt.Status = types.ReceiptStatusFailed
} else {
receipt.Status = types.ReceiptStatusSuccessful
}
return receipt, err
}
func prepareNonceManagerMessage(baseTx *types.Transaction, chainConfig *params.ChainConfig) *Message {
tx := baseTx.Rip7560TransactionData()
key := make([]byte, 32)
fromBig, _ := uint256.FromBig(tx.BigNonce)
fromBig.WriteToSlice(key)
nonceManagerData := make([]byte, 0)
nonceManagerData = append(nonceManagerData[:], tx.Sender.Bytes()...)
nonceManagerData = append(nonceManagerData[:], key...)
return &Message{
From: chainConfig.EntryPointAddress,
To: &chainConfig.NonceManagerAddress,
Value: big.NewInt(0),
GasLimit: 100000,
GasPrice: tx.GasFeeCap,
GasFeeCap: tx.GasFeeCap,
GasTipCap: tx.GasTipCap,
Data: nonceManagerData,
AccessList: make(types.AccessList, 0),
SkipAccountChecks: true,
IsRip7560Frame: true,
}
}
func prepareDeployerMessage(baseTx *types.Transaction, config *params.ChainConfig) *Message {
tx := baseTx.Rip7560TransactionData()
if len(tx.DeployerData) < 20 {
return nil
}
var deployerAddress common.Address = [20]byte(tx.DeployerData[0:20])
return &Message{
From: config.DeployerCallerAddress,
To: &deployerAddress,
Value: big.NewInt(0),
GasLimit: tx.ValidationGas,
GasPrice: tx.GasFeeCap,
GasFeeCap: tx.GasFeeCap,
GasTipCap: tx.GasTipCap,
Data: tx.DeployerData[20:],
AccessList: make(types.AccessList, 0),
SkipAccountChecks: true,
IsRip7560Frame: true,
}
}
func prepareAccountValidationMessage(baseTx *types.Transaction, chainConfig *params.ChainConfig, signingHash common.Hash, deploymentUsedGas uint64) (*Message, error) {
tx := baseTx.Rip7560TransactionData()
jsondata := `[
{"type":"function","name":"validateTransaction","inputs": [{"name": "version","type": "uint256"},{"name": "txHash","type": "bytes32"},{"name": "transaction","type": "bytes"}]}
]`
validateTransactionAbi, err := abi.JSON(strings.NewReader(jsondata))
if err != nil {
return nil, err
}
txAbiEncoding, err := tx.AbiEncode()
validateTransactionData, err := validateTransactionAbi.Pack("validateTransaction", big.NewInt(0), signingHash, txAbiEncoding)
return &Message{
From: chainConfig.EntryPointAddress,
To: tx.Sender,
Value: big.NewInt(0),
GasLimit: tx.ValidationGas - deploymentUsedGas,
GasPrice: tx.GasFeeCap,
GasFeeCap: tx.GasFeeCap,
GasTipCap: tx.GasTipCap,
Data: validateTransactionData,
AccessList: make(types.AccessList, 0),
SkipAccountChecks: true,
IsRip7560Frame: true,
}, nil
}
func preparePaymasterValidationMessage(baseTx *types.Transaction, config *params.ChainConfig, signingHash common.Hash) (*Message, error) {
tx := baseTx.Rip7560TransactionData()
if len(tx.PaymasterData) < 20 {
return nil, nil
}
var paymasterAddress common.Address = [20]byte(tx.PaymasterData[0:20])
jsondata := `[
{"type":"function","name":"validatePaymasterTransaction","inputs": [{"name": "version","type": "uint256"},{"name": "txHash","type": "bytes32"},{"name": "transaction","type": "bytes"}]}
]`
validateTransactionAbi, err := abi.JSON(strings.NewReader(jsondata))
txAbiEncoding, err := tx.AbiEncode()
data, err := validateTransactionAbi.Pack("validatePaymasterTransaction", big.NewInt(0), signingHash, txAbiEncoding)
if err != nil {
return nil, err
}
return &Message{
From: config.EntryPointAddress,
To: &paymasterAddress,
Value: big.NewInt(0),
GasLimit: tx.PaymasterGas,
GasPrice: tx.GasFeeCap,
GasFeeCap: tx.GasFeeCap,
GasTipCap: tx.GasTipCap,
Data: data,
AccessList: make(types.AccessList, 0),
SkipAccountChecks: true,
IsRip7560Frame: true,
}, nil
}
func prepareAccountExecutionMessage(baseTx *types.Transaction, config *params.ChainConfig) *Message {
tx := baseTx.Rip7560TransactionData()
return &Message{
From: config.EntryPointAddress,
To: tx.Sender,
Value: big.NewInt(0),
GasLimit: tx.Gas,
GasPrice: tx.GasFeeCap,
GasFeeCap: tx.GasFeeCap,
GasTipCap: tx.GasTipCap,
Data: tx.Data,
AccessList: make(types.AccessList, 0),
SkipAccountChecks: true,
IsRip7560Frame: true,
}
}
func prepareEOATargetExecutionMessage(baseTx *types.Transaction) (*Message, error) {
tx := baseTx.Rip7560TransactionData()
if len(tx.Data) < 20 {
return nil, errors.New("RIP-7560 sent by an EOA but the transaction data is too short")
}
var to common.Address = [20]byte(tx.Data[0:20])
return &Message{
From: *tx.Sender,
To: &to,
Value: tx.Value,
GasLimit: tx.Gas,
GasPrice: tx.GasFeeCap,
GasFeeCap: tx.GasFeeCap,
GasTipCap: tx.GasTipCap,
Data: tx.Data[20:],
AccessList: make(types.AccessList, 0),
SkipAccountChecks: true,
IsRip7560Frame: true,
}, nil
}
func preparePostOpMessage(vpr *ValidationPhaseResult, chainConfig *params.ChainConfig, executionResult *ExecutionResult) (*Message, error) {
if len(vpr.PaymasterContext) == 0 {
return nil, nil
}
tx := vpr.Tx.Rip7560TransactionData()
jsondata := `[
{"type":"function","name":"postPaymasterTransaction","inputs": [{"name": "success","type": "bool"},{"name": "actualGasCost","type": "uint256"},{"name": "context","type": "bytes"}]}
]`
postPaymasterTransactionAbi, err := abi.JSON(strings.NewReader(jsondata))
if err != nil {
return nil, err
}
postOpData, err := postPaymasterTransactionAbi.Pack("postPaymasterTransaction", true, big.NewInt(0), vpr.PaymasterContext)
if err != nil {
return nil, err
}
var paymasterAddress common.Address = [20]byte(tx.PaymasterData[0:20])
return &Message{
From: chainConfig.EntryPointAddress,
To: &paymasterAddress,
Value: big.NewInt(0),
GasLimit: tx.PaymasterGas - executionResult.UsedGas,
GasPrice: tx.GasFeeCap,
GasFeeCap: tx.GasFeeCap,
GasTipCap: tx.GasTipCap,
Data: postOpData,
AccessList: tx.AccessList,
SkipAccountChecks: true,
IsRip7560Frame: true,
}, nil
}
func validateAccountReturnData(data []byte) (uint64, uint64, error) {
MAGIC_VALUE_SENDER := uint32(0xbf45c166)
if len(data) != 32 {
return 0, 0, errors.New("invalid account return data length")
}
magicExpected := binary.BigEndian.Uint32(data[:4])
if magicExpected != MAGIC_VALUE_SENDER {
return 0, 0, errors.New("account did not return correct MAGIC_VALUE")
}
validAfter := binary.BigEndian.Uint64(data[4:12])
validUntil := binary.BigEndian.Uint64(data[12:20])
return validAfter, validUntil, nil
}
func validatePaymasterReturnData(data []byte) ([]byte, uint64, uint64, error) {
MAGIC_VALUE_PAYMASTER := uint32(0xe0e6183a)
if len(data) < 4 {
return nil, 0, 0, errors.New("invalid paymaster return data length")
}
magicExpected := binary.BigEndian.Uint32(data[:4])
if magicExpected != MAGIC_VALUE_PAYMASTER {
return nil, 0, 0, errors.New("paymaster did not return correct MAGIC_VALUE")
}
jsondata := `[
{"type":"function","name":"validatePaymasterTransaction","outputs": [{"name": "context","type": "bytes"},{"name": "validUntil","type": "uint256"},{"name": "validAfter","type": "uint256"}]}
]`
validatePaymasterTransactionAbi, err := abi.JSON(strings.NewReader(jsondata))
if err != nil {
// todo: wrap error message
return nil, 0, 0, err
}
decodedPmReturnData, err := validatePaymasterTransactionAbi.Unpack("validatePaymasterTransaction", data[4:])
if err != nil {
return nil, 0, 0, err
}
context := decodedPmReturnData[0].([]byte)
validAfter := decodedPmReturnData[1].(*big.Int)
validUntil := decodedPmReturnData[2].(*big.Int)
return context, validAfter.Uint64(), validUntil.Uint64(), nil
}
func validateValidityTimeRange(time uint64, validAfter uint64, validUntil uint64) error {
if validUntil == 0 && validAfter == 0 {
return nil
}
if validUntil < validAfter {
return errors.New("RIP-7560 transaction validity range invalid")
}
if time > validUntil {
return errors.New("RIP-7560 transaction validity expired")
}
if time < validAfter {
return errors.New("RIP-7560 transaction validity not reached yet")
}
return nil
}

View file

@ -146,6 +146,7 @@ type Message struct {
// account nonce in state. It also disables checking that the sender is an EOA.
// This field will be set to true for operations like RPC eth_call.
SkipAccountChecks bool
IsRip7560Frame bool
}
// TransactionToMessage converts a transaction into a Message.
@ -213,6 +214,7 @@ type StateTransition struct {
initialGas uint64
state vm.StateDB
evm *vm.EVM
rip7560Frame bool
}
// NewStateTransition initialises and returns a new state transition object.

View file

@ -1653,3 +1653,18 @@ func (p *BlobPool) Status(hash common.Hash) txpool.TxStatus {
}
return txpool.TxStatusUnknown
}
func (pool *BlobPool) SubmitRip7560Bundle(_ *types.ExternallyReceivedBundle) error {
// nothing to do here
return nil
}
func (pool *BlobPool) GetRip7560BundleStatus(_ common.Hash) (*types.BundleReceipt, error) {
// nothing to do here
return nil, nil
}
func (pool *BlobPool) PendingRip7560Bundle() (*types.ExternallyReceivedBundle, error) {
// nothing to do here
return nil, nil
}

View file

@ -117,6 +117,8 @@ type BlockChain interface {
// StateAt returns a state database for a given root hash (generally the head).
StateAt(root common.Hash) (*state.StateDB, error)
GetReceiptsByHash(hash common.Hash) types.Receipts
}
// Config are the configuration parameters of the transaction pool.
@ -1959,3 +1961,18 @@ func (t *lookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
func numSlots(tx *types.Transaction) int {
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
}
func (pool *LegacyPool) SubmitRip7560Bundle(_ *types.ExternallyReceivedBundle) error {
// nothing to do here
return nil
}
func (pool *LegacyPool) GetRip7560BundleStatus(_ common.Hash) (*types.BundleReceipt, error) {
// nothing to do here
return nil, nil
}
func (pool *LegacyPool) PendingRip7560Bundle() (*types.ExternallyReceivedBundle, error) {
// nothing to do here
return nil, nil
}

View file

@ -0,0 +1,273 @@
package rip7560pool
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"math/big"
"sync"
"sync/atomic"
)
type Config struct {
MaxBundleSize uint
MaxBundleGas uint
}
// Rip7560BundlerPool is the transaction pool dedicated to RIP-7560 AA transactions.
// This implementation relies on an external bundler process to perform most of the hard work.
type Rip7560BundlerPool struct {
config Config
chain legacypool.BlockChain
txFeed event.Feed
currentHead atomic.Pointer[types.Header] // Current head of the blockchain
pendingBundles []*types.ExternallyReceivedBundle
includedBundles map[common.Hash]*types.BundleReceipt
mu sync.Mutex
coinbase common.Address
}
func (pool *Rip7560BundlerPool) Init(_ uint64, head *types.Header, _ txpool.AddressReserver) error {
pool.pendingBundles = make([]*types.ExternallyReceivedBundle, 0)
pool.includedBundles = make(map[common.Hash]*types.BundleReceipt)
pool.currentHead.Store(head)
return nil
}
func (pool *Rip7560BundlerPool) Close() error {
return nil
}
func (pool *Rip7560BundlerPool) Reset(oldHead, newHead *types.Header) {
pool.mu.Lock()
defer pool.mu.Unlock()
newIncludedBundles := pool.gatherIncludedBundlesStats(newHead)
for _, included := range newIncludedBundles {
pool.includedBundles[included.BundleHash] = included
}
pendingBundles := make([]*types.ExternallyReceivedBundle, 0, len(pool.pendingBundles))
for _, bundle := range pool.pendingBundles {
nextBlock := big.NewInt(0).Add(newHead.Number, big.NewInt(1))
if bundle.ValidForBlock.Cmp(nextBlock) == 0 {
pendingBundles = append(pendingBundles, bundle)
}
}
pool.pendingBundles = pendingBundles
pool.currentHead.Store(newHead)
}
// For simplicity, this function assumes 'Reset' called for each new block sequentially.
func (pool *Rip7560BundlerPool) gatherIncludedBundlesStats(newHead *types.Header) map[common.Hash]*types.BundleReceipt {
// 1. Is there a bundle included in the block?
// note that in 'clique' mode Coinbase is always set to 0x000...000
if newHead.Coinbase.Cmp(pool.coinbase) != 0 && newHead.Coinbase.Cmp(common.Address{}) != 0 {
// not our block
return nil
}
// get all transaction hashes in block
add := pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64())
block := add.Transactions()
receipts := pool.chain.GetReceiptsByHash(add.Hash())
// match transactions in block to bundle ?
includedBundles := make(map[common.Hash]*types.BundleReceipt)
// 'pendingBundles' length is expected to be single digits, probably a single bundle in most cases
for _, bundle := range pool.pendingBundles {
if len(block) < len(bundle.Transactions) {
// this bundle does not even fit this block
continue
}
for i := 0; i < len(block); i++ {
transactions := make(types.Transactions, 0)
for j := 0; j < len(bundle.Transactions); j++ {
blockTx := block[i]
bundleTx := bundle.Transactions[j]
if bundleTx.Hash().Cmp(blockTx.Hash()) == 0 {
// tx hash has matched
transactions = append(transactions, blockTx)
if j == len(bundle.Transactions)-1 {
// FOUND BUNDLE IN BLOCK
receipt := createBundleReceipt(add, bundle.BundleHash, transactions, receipts)
includedBundles[bundle.BundleHash] = receipt
} else {
// let's see if next tx in bundle matches
i++
}
}
}
}
}
return includedBundles
}
func createBundleReceipt(block *types.Block, BundleHash common.Hash, transactions types.Transactions, blockReceipts types.Receipts) *types.BundleReceipt {
receipts := make(types.Receipts, 0)
OuterLoop:
for _, transaction := range transactions {
for _, receipt := range blockReceipts {
if receipt.TxHash == transaction.Hash() {
receipts = append(receipts, receipt)
continue OuterLoop
}
}
panic("receipt not found for transaction")
}
var gasUsed uint64 = 0
var gasPaidPriority = big.NewInt(0)
for _, receipt := range receipts {
gasUsed += receipt.GasUsed
priorityFeePerGas := big.NewInt(0).Sub(receipt.EffectiveGasPrice, block.BaseFee())
priorityFeePaid := big.NewInt(0).Mul(big.NewInt(int64(gasUsed)), priorityFeePerGas)
gasPaidPriority = big.NewInt(0).Add(gasPaidPriority, priorityFeePaid)
}
return &types.BundleReceipt{
BundleHash: BundleHash,
Count: uint64(len(transactions)),
Status: 0,
BlockNumber: block.NumberU64(),
BlockHash: block.Hash(),
TransactionReceipts: receipts,
GasUsed: gasUsed,
GasPaidPriority: gasPaidPriority,
BlockTimestamp: block.Time(),
}
}
// SetGasTip is ignored by the External Bundler AA sub pool.
func (pool *Rip7560BundlerPool) SetGasTip(_ *big.Int) {}
func (pool *Rip7560BundlerPool) Has(hash common.Hash) bool {
pool.mu.Lock()
defer pool.mu.Unlock()
tx := pool.Get(hash)
return tx != nil
}
func (pool *Rip7560BundlerPool) Get(hash common.Hash) *types.Transaction {
pool.mu.Lock()
defer pool.mu.Unlock()
for _, bundle := range pool.pendingBundles {
for _, tx := range bundle.Transactions {
if tx.Hash().Cmp(hash) == 0 {
return tx
}
}
}
return nil
}
func (pool *Rip7560BundlerPool) Add(_ []*types.Transaction, _ bool, _ bool) []error {
return nil
}
func (pool *Rip7560BundlerPool) Pending(_ txpool.PendingFilter) map[common.Address][]*txpool.LazyTransaction {
return nil
}
func (pool *Rip7560BundlerPool) PendingRip7560Bundle() (*types.ExternallyReceivedBundle, error) {
pool.mu.Lock()
defer pool.mu.Unlock()
bundle := pool.selectExternalBundle()
return bundle, nil
}
// SubscribeTransactions is not needed for the External Bundler AA sub pool and 'ch' will never be sent anything.
func (pool *Rip7560BundlerPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, _ bool) event.Subscription {
return pool.txFeed.Subscribe(ch)
}
// Nonce is only used from 'GetPoolNonce' which is not relevant for AA transactions.
func (pool *Rip7560BundlerPool) Nonce(_ common.Address) uint64 {
return 0
}
// Stats function not implemented for the External Bundler AA sub pool.
func (pool *Rip7560BundlerPool) Stats() (int, int) {
return 0, 0
}
// Content function not implemented for the External Bundler AA sub pool.
func (pool *Rip7560BundlerPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
return nil, nil
}
// ContentFrom function not implemented for the External Bundler AA sub pool.
func (pool *Rip7560BundlerPool) ContentFrom(_ common.Address) ([]*types.Transaction, []*types.Transaction) {
return nil, nil
}
// Locals are not necessary for AA Pool
func (pool *Rip7560BundlerPool) Locals() []common.Address {
return []common.Address{}
}
func (pool *Rip7560BundlerPool) Status(_ common.Hash) txpool.TxStatus {
panic("implement me")
}
// New creates a new RIP-7560 Account Abstraction Bundler transaction pool.
func New(config Config, chain legacypool.BlockChain, coinbase common.Address) *Rip7560BundlerPool {
return &Rip7560BundlerPool{
config: config,
chain: chain,
coinbase: coinbase,
}
}
// Filter rejects all individual transactions for External Bundler AA sub pool.
func (pool *Rip7560BundlerPool) Filter(_ *types.Transaction) bool {
return false
}
func (pool *Rip7560BundlerPool) SubmitRip7560Bundle(bundle *types.ExternallyReceivedBundle) error {
pool.mu.Lock()
defer pool.mu.Unlock()
currentBlock := pool.currentHead.Load().Number
nextBlock := big.NewInt(0).Add(currentBlock, big.NewInt(1))
log.Error("RIP-7560 bundle submitted", "validForBlock", bundle.ValidForBlock.String(), "nextBlock", nextBlock.String())
pool.pendingBundles = append(pool.pendingBundles, bundle)
if nextBlock.Cmp(bundle.ValidForBlock) == 0 {
pool.txFeed.Send(core.NewTxsEvent{Txs: bundle.Transactions})
}
return nil
}
func (pool *Rip7560BundlerPool) GetRip7560BundleStatus(hash common.Hash) (*types.BundleReceipt, error) {
pool.mu.Lock()
defer pool.mu.Unlock()
return pool.includedBundles[hash], nil
}
// Simply returns the bundle with the highest promised revenue by fully trusting the bundler-provided value.
func (pool *Rip7560BundlerPool) selectExternalBundle() *types.ExternallyReceivedBundle {
var selectedBundle *types.ExternallyReceivedBundle
for _, bundle := range pool.pendingBundles {
if selectedBundle == nil || selectedBundle.ExpectedRevenue.Cmp(bundle.ExpectedRevenue) == -1 {
selectedBundle = bundle
}
}
return selectedBundle
}

View file

@ -162,4 +162,10 @@ type SubPool interface {
// Status returns the known status (unknown/pending/queued) of a transaction
// identified by their hashes.
Status(hash common.Hash) TxStatus
// RIP-7560 specific subpool functions, other subpools should ignore these
SubmitRip7560Bundle(bundle *types.ExternallyReceivedBundle) error
GetRip7560BundleStatus(hash common.Hash) (*types.BundleReceipt, error)
PendingRip7560Bundle() (*types.ExternallyReceivedBundle, error)
}

View file

@ -0,0 +1,46 @@
package txpool
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// SubmitBundle inserts the entire bundle of Type 4 transactions into the relevant pool.
func (p *TxPool) SubmitRip7560Bundle(bundle *types.ExternallyReceivedBundle) error {
// todo: we cannot 'filter-out' the AA pool so just passing to all pools - only AA pool has code in SubmitBundle
for _, subpool := range p.subpools {
err := subpool.SubmitRip7560Bundle(bundle)
if err != nil {
return err
}
}
return nil
}
func (p *TxPool) GetRip7560BundleStatus(hash common.Hash) (*types.BundleReceipt, error) {
// todo: we cannot 'filter-out' the AA pool so just passing to all pools - only AA pool has code in SubmitBundle
for _, subpool := range p.subpools {
bundleStats, err := subpool.GetRip7560BundleStatus(hash)
if err != nil {
return nil, err
}
if bundleStats != nil {
return bundleStats, nil
}
}
return nil, nil
}
func (p *TxPool) PendingRip7560Bundle() (*types.ExternallyReceivedBundle, error) {
// todo: we cannot 'filter-out' the AA pool so just passing to all pools - only AA pool has code in PendingBundle
for _, subpool := range p.subpools {
pendingBundle, err := subpool.PendingRip7560Bundle()
if err != nil {
return nil, err
}
if pendingBundle != nil {
return pendingBundle, nil
}
}
return nil, nil
}

View file

@ -49,6 +49,7 @@ const (
AccessListTxType = 0x01
DynamicFeeTxType = 0x02
BlobTxType = 0x03
Rip7560Type = 0x04
)
// Transaction is an Ethereum transaction.
@ -466,6 +467,12 @@ func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction {
return cpy
}
func (tx *Transaction) Rip7560TransactionData() *Rip7560AccountAbstractionTx {
inner := tx.inner
ptr := inner.(*Rip7560AccountAbstractionTx)
return ptr
}
// SetTime sets the decoding time of a transaction. This is used by tests to set
// arbitrary times and by persistent transaction pools when loading old txs from
// disk.

221
core/types/tx_rip7560.go Normal file
View file

@ -0,0 +1,221 @@
// 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 (
"bytes"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
"math/big"
)
const ScaTransactionSubtype = 0x01
// Rip7560AccountAbstractionTx represents an RIP-7560 transaction.
type Rip7560AccountAbstractionTx struct {
Subtype byte
// overlapping fields
ChainID *big.Int
GasTipCap *big.Int // a.k.a. maxPriorityFeePerGas
GasFeeCap *big.Int // a.k.a. maxFeePerGas
Gas uint64
Data []byte
AccessList AccessList
// extra fields
Sender *common.Address
Signature []byte
PaymasterData []byte
DeployerData []byte
BuilderFee *big.Int
ValidationGas uint64
PaymasterGas uint64
PostOpGas uint64
BigNonce *big.Int
// removed fields
To *common.Address
Nonce uint64
Value *big.Int
}
// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *Rip7560AccountAbstractionTx) copy() TxData {
cpy := &Rip7560AccountAbstractionTx{
Subtype: tx.Subtype,
To: copyAddressPtr(tx.To),
Data: common.CopyBytes(tx.Data),
Gas: tx.Gas,
// These are copied below.
AccessList: make(AccessList, len(tx.AccessList)),
Value: new(big.Int),
ChainID: new(big.Int),
GasTipCap: new(big.Int),
GasFeeCap: new(big.Int),
BigNonce: new(big.Int),
Sender: copyAddressPtr(tx.Sender),
Signature: common.CopyBytes(tx.Signature),
PaymasterData: common.CopyBytes(tx.PaymasterData),
DeployerData: common.CopyBytes(tx.DeployerData),
BuilderFee: new(big.Int),
ValidationGas: tx.ValidationGas,
PaymasterGas: tx.PaymasterGas,
}
copy(cpy.AccessList, tx.AccessList)
if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
if tx.ChainID != nil {
cpy.ChainID.Set(tx.ChainID)
}
if tx.GasTipCap != nil {
cpy.GasTipCap.Set(tx.GasTipCap)
}
if tx.GasFeeCap != nil {
cpy.GasFeeCap.Set(tx.GasFeeCap)
}
if tx.BigNonce != nil {
cpy.BigNonce.Set(tx.BigNonce)
}
if tx.BuilderFee != nil {
cpy.BuilderFee.Set(tx.BuilderFee)
}
return cpy
}
// accessors for innerTx.
func (tx *Rip7560AccountAbstractionTx) txType() byte { return Rip7560Type }
func (tx *Rip7560AccountAbstractionTx) chainID() *big.Int { return tx.ChainID }
func (tx *Rip7560AccountAbstractionTx) accessList() AccessList { return tx.AccessList }
func (tx *Rip7560AccountAbstractionTx) data() []byte { return tx.Data }
func (tx *Rip7560AccountAbstractionTx) gas() uint64 { return tx.Gas }
func (tx *Rip7560AccountAbstractionTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
func (tx *Rip7560AccountAbstractionTx) gasTipCap() *big.Int { return tx.GasTipCap }
func (tx *Rip7560AccountAbstractionTx) gasPrice() *big.Int { return tx.GasFeeCap }
func (tx *Rip7560AccountAbstractionTx) value() *big.Int { return tx.Value }
func (tx *Rip7560AccountAbstractionTx) nonce() uint64 { return 0 }
func (tx *Rip7560AccountAbstractionTx) bigNonce() *big.Int { return tx.BigNonce }
func (tx *Rip7560AccountAbstractionTx) to() *common.Address { return tx.To }
func (tx *Rip7560AccountAbstractionTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
if baseFee == nil {
return dst.Set(tx.GasFeeCap)
}
tip := dst.Sub(tx.GasFeeCap, baseFee)
if tip.Cmp(tx.GasTipCap) > 0 {
tip.Set(tx.GasTipCap)
}
return tip.Add(tip, baseFee)
}
func (tx *Rip7560AccountAbstractionTx) rawSignatureValues() (v, r, s *big.Int) {
return new(big.Int), new(big.Int), new(big.Int)
}
func (tx *Rip7560AccountAbstractionTx) setSignatureValues(chainID, v, r, s *big.Int) {
//tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
}
// encode the subtype byte and the payload-bearing bytes of the RIP-7560 transaction
func (tx *Rip7560AccountAbstractionTx) encode(b *bytes.Buffer) error {
b.WriteByte(ScaTransactionSubtype)
return rlp.Encode(b, tx)
}
// decode the payload-bearing bytes of the encoded RIP-7560 transaction payload
func (tx *Rip7560AccountAbstractionTx) decode(input []byte) error {
tx.Subtype = ScaTransactionSubtype
return rlp.DecodeBytes(input[1:], tx)
}
// Rip7560Transaction an equivalent of a solidity struct only used to encode the 'transaction' parameter
type Rip7560Transaction struct {
Sender common.Address
Nonce *big.Int
ValidationGasLimit *big.Int
PaymasterGasLimit *big.Int
CallGasLimit *big.Int
MaxFeePerGas *big.Int
MaxPriorityFeePerGas *big.Int
BuilderFee *big.Int
PaymasterData []byte
DeployerData []byte
CallData []byte
Signature []byte
}
func (tx *Rip7560AccountAbstractionTx) AbiEncode() ([]byte, error) {
structThing, _ := abi.NewType("tuple", "struct thing", []abi.ArgumentMarshaling{
{Name: "sender", Type: "address"},
{Name: "nonce", Type: "uint256"},
{Name: "validationGasLimit", Type: "uint256"},
{Name: "paymasterGasLimit", Type: "uint256"},
{Name: "callGasLimit", Type: "uint256"},
{Name: "maxFeePerGas", Type: "uint256"},
{Name: "maxPriorityFeePerGas", Type: "uint256"},
{Name: "builderFee", Type: "uint256"},
{Name: "paymasterData", Type: "bytes"},
{Name: "deployerData", Type: "bytes"},
{Name: "callData", Type: "bytes"},
{Name: "signature", Type: "bytes"},
})
args := abi.Arguments{
{Type: structThing, Name: "param_one"},
}
record := &Rip7560Transaction{
Sender: *tx.Sender,
Nonce: tx.BigNonce,
ValidationGasLimit: big.NewInt(int64(tx.ValidationGas)),
PaymasterGasLimit: big.NewInt(int64(tx.PaymasterGas)),
CallGasLimit: big.NewInt(int64(tx.Gas)),
MaxFeePerGas: tx.GasFeeCap,
MaxPriorityFeePerGas: tx.GasTipCap,
BuilderFee: tx.BuilderFee,
PaymasterData: tx.PaymasterData,
DeployerData: tx.DeployerData,
CallData: tx.Data,
Signature: tx.Signature,
}
packed, err := args.Pack(&record)
return packed, err
}
// ExternallyReceivedBundle represents a bundle of Type 4 transactions received from a trusted 3rd party.
// The validator includes the bundle in the original order atomically or drops it completely.
type ExternallyReceivedBundle struct {
BundlerId string
BundleHash common.Hash
ExpectedRevenue *big.Int
ValidForBlock *big.Int
Transactions []*Transaction
}
// BundleReceipt represents a receipt for an ExternallyReceivedBundle successfully included in a block.
type BundleReceipt struct {
BundleHash common.Hash
Count uint64
Status uint64 // 0=included / 1=pending / 2=invalid / 3=unknown
BlockNumber uint64
BlockHash common.Hash
TransactionReceipts []*Receipt
GasUsed uint64
GasPaidPriority *big.Int
BlockTimestamp uint64
}

View file

@ -0,0 +1,15 @@
package eth
import (
"context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
func (b *EthAPIBackend) SubmitRip7560Bundle(bundle *types.ExternallyReceivedBundle) error {
return b.eth.txPool.SubmitRip7560Bundle(bundle)
}
func (b *EthAPIBackend) GetRip7560BundleStatus(ctx context.Context, hash common.Hash) (*types.BundleReceipt, error) {
return b.eth.txPool.GetRip7560BundleStatus(hash)
}

View file

@ -21,6 +21,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/core/txpool/rip7560pool"
"math/big"
"runtime"
"sync"
@ -240,7 +241,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}
legacyPool := legacypool.New(config.TxPool, eth.blockchain)
eth.txPool, err = txpool.New(config.TxPool.PriceLimit, eth.blockchain, []txpool.SubPool{legacyPool, blobPool})
rip7560PoolConfig := rip7560pool.Config{
MaxBundleGas: 10000000,
MaxBundleSize: 100,
}
rip7560 := rip7560pool.New(rip7560PoolConfig, eth.blockchain, config.Miner.Etherbase)
eth.txPool, err = txpool.New(config.TxPool.PriceLimit, eth.blockchain, []txpool.SubPool{legacyPool, blobPool, rip7560})
if err != nil {
return nil, err
}

View file

@ -97,6 +97,10 @@ type Backend interface {
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
BloomStatus() (uint64, uint64)
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
// RIP-7560 specific functions
SubmitRip7560Bundle(bundle *types.ExternallyReceivedBundle) error
GetRip7560BundleStatus(ctx context.Context, hash common.Hash) (*types.BundleReceipt, error)
}
func GetAPIs(apiBackend Backend) []rpc.API {

View file

@ -0,0 +1,62 @@
package ethapi
import (
"context"
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"golang.org/x/crypto/sha3"
"math/big"
)
func (s *TransactionAPI) SendRip7560TransactionsBundle(ctx context.Context, args []TransactionArgs, creationBlock *big.Int, expectedRevenue *big.Int, bundlerId string) (common.Hash, error) {
if len(args) == 0 {
return common.Hash{}, errors.New("submitted bundle has zero length")
}
txs := make([]*types.Transaction, len(args))
for i := 0; i < len(args); i++ {
txs[i] = args[i].ToTransaction()
}
bundle := &types.ExternallyReceivedBundle{
BundlerId: bundlerId,
ExpectedRevenue: expectedRevenue,
ValidForBlock: creationBlock,
Transactions: txs,
}
bundleHash := calculateBundleHash(txs)
bundle.BundleHash = bundleHash
err := SubmitRip7560Bundle(ctx, s.b, bundle)
if err != nil {
return common.Hash{}, err
}
return bundleHash, nil
}
func (s *TransactionAPI) GetRip7560BundleStatus(ctx context.Context, hash common.Hash) (*types.BundleReceipt, error) {
bundleStats, err := s.b.GetRip7560BundleStatus(ctx, hash)
return bundleStats, err
}
// TODO: If this code is indeed necessary, keep it in utils; better - remove altogether.
func calculateBundleHash(txs []*types.Transaction) common.Hash {
appendedTxIds := make([]byte, 0)
for _, tx := range txs {
txHash := tx.Hash()
appendedTxIds = append(appendedTxIds, txHash[:]...)
}
return rlpHash(appendedTxIds)
}
func rlpHash(x interface{}) (h common.Hash) {
hw := sha3.NewLegacyKeccak256()
rlp.Encode(hw, x)
hw.Sum(h[:0])
return h
}
// SubmitRip7560Bundle is a helper function that submits a bundle of Type 4 transactions to txPool and logs a message.
func SubmitRip7560Bundle(ctx context.Context, b Backend, bundle *types.ExternallyReceivedBundle) error {
return b.SubmitRip7560Bundle(bundle)
}

View file

@ -373,6 +373,22 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
return nil
}
func (miner *Miner) commitRip7560TransactionsBundle(env *environment, txs *types.ExternallyReceivedBundle, _ *atomic.Int32) error {
// todo: copied over to fix crash, probably should do it once
gasLimit := env.header.GasLimit
if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(gasLimit)
}
validatedTxs, receipts, _, err := core.HandleRip7560Transactions(txs.Transactions, 0, env.state, &env.coinbase, env.header, env.gasPool, miner.chainConfig, miner.chain, vm.Config{})
env.txs = append(env.txs, validatedTxs...)
env.receipts = append(env.receipts, receipts...)
env.tcount += len(validatedTxs)
return err
}
// fillTransactions retrieves the pending transactions from the txpool and fills them
// into the given sealing block. The transaction selection and ordering strategy can
// be customized with the plugin in the future.
@ -411,6 +427,14 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment)
localBlobTxs[account] = txs
}
}
pendingBundle, err := miner.txpool.PendingRip7560Bundle()
if pendingBundle != nil {
if err = miner.commitRip7560TransactionsBundle(env, pendingBundle, interrupt); err != nil {
return err
}
}
// Fill the block with all available pending transactions.
if len(localPlainTxs) > 0 || len(localBlobTxs) > 0 {
plainTxs := newTransactionsByPriceAndNonce(env.signer, localPlainTxs, env.header.BaseFee)

View file

@ -368,6 +368,11 @@ type ChainConfig struct {
// Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"`
Clique *CliqueConfig `json:"clique,omitempty"`
// RIP-7560 specific config parameters
EntryPointAddress common.Address `json:"entryPointAddress,omitempty"`
NonceManagerAddress common.Address `json:"nonceManagerAddress,omitempty"`
DeployerCallerAddress common.Address `json:"deployerCallerAddress,omitempty"`
}
// EthashConfig is the consensus engine configs for proof-of-work based sealing.