mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
WIP: manually bring over RIP-7560 related code; compiles but does not run
This commit is contained in:
parent
4bdbaab471
commit
9da00cd8c0
15 changed files with 1358 additions and 1 deletions
653
core/state_processor_rip7560.go
Normal file
653
core/state_processor_rip7560.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -146,6 +146,7 @@ type Message struct {
|
||||||
// account nonce in state. It also disables checking that the sender is an EOA.
|
// 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.
|
// This field will be set to true for operations like RPC eth_call.
|
||||||
SkipAccountChecks bool
|
SkipAccountChecks bool
|
||||||
|
IsRip7560Frame bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionToMessage converts a transaction into a Message.
|
// TransactionToMessage converts a transaction into a Message.
|
||||||
|
|
@ -213,6 +214,7 @@ type StateTransition struct {
|
||||||
initialGas uint64
|
initialGas uint64
|
||||||
state vm.StateDB
|
state vm.StateDB
|
||||||
evm *vm.EVM
|
evm *vm.EVM
|
||||||
|
rip7560Frame bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStateTransition initialises and returns a new state transition object.
|
// NewStateTransition initialises and returns a new state transition object.
|
||||||
|
|
|
||||||
|
|
@ -1653,3 +1653,18 @@ func (p *BlobPool) Status(hash common.Hash) txpool.TxStatus {
|
||||||
}
|
}
|
||||||
return txpool.TxStatusUnknown
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,8 @@ type BlockChain interface {
|
||||||
|
|
||||||
// StateAt returns a state database for a given root hash (generally the head).
|
// StateAt returns a state database for a given root hash (generally the head).
|
||||||
StateAt(root common.Hash) (*state.StateDB, error)
|
StateAt(root common.Hash) (*state.StateDB, error)
|
||||||
|
|
||||||
|
GetReceiptsByHash(hash common.Hash) types.Receipts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config are the configuration parameters of the transaction pool.
|
// 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 {
|
func numSlots(tx *types.Transaction) int {
|
||||||
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
273
core/txpool/rip7560pool/rip7560pool.go
Normal file
273
core/txpool/rip7560pool/rip7560pool.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -162,4 +162,10 @@ type SubPool interface {
|
||||||
// Status returns the known status (unknown/pending/queued) of a transaction
|
// Status returns the known status (unknown/pending/queued) of a transaction
|
||||||
// identified by their hashes.
|
// identified by their hashes.
|
||||||
Status(hash common.Hash) TxStatus
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
core/txpool/txpool_rip7560.go
Normal file
46
core/txpool/txpool_rip7560.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -49,6 +49,7 @@ const (
|
||||||
AccessListTxType = 0x01
|
AccessListTxType = 0x01
|
||||||
DynamicFeeTxType = 0x02
|
DynamicFeeTxType = 0x02
|
||||||
BlobTxType = 0x03
|
BlobTxType = 0x03
|
||||||
|
Rip7560Type = 0x04
|
||||||
)
|
)
|
||||||
|
|
||||||
// Transaction is an Ethereum transaction.
|
// Transaction is an Ethereum transaction.
|
||||||
|
|
@ -466,6 +467,12 @@ func (tx *Transaction) WithBlobTxSidecar(sideCar *BlobTxSidecar) *Transaction {
|
||||||
return cpy
|
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
|
// 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
|
// arbitrary times and by persistent transaction pools when loading old txs from
|
||||||
// disk.
|
// disk.
|
||||||
|
|
|
||||||
221
core/types/tx_rip7560.go
Normal file
221
core/types/tx_rip7560.go
Normal 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
|
||||||
|
}
|
||||||
15
eth/api_backend_rip7560.go
Normal file
15
eth/api_backend_rip7560.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/core/txpool/rip7560pool"
|
||||||
"math/big"
|
"math/big"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -240,7 +241,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
}
|
}
|
||||||
legacyPool := legacypool.New(config.TxPool, eth.blockchain)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,10 @@ type Backend interface {
|
||||||
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
||||||
BloomStatus() (uint64, uint64)
|
BloomStatus() (uint64, uint64)
|
||||||
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
|
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 {
|
func GetAPIs(apiBackend Backend) []rpc.API {
|
||||||
|
|
|
||||||
62
internal/ethapi/rip7560api.go
Normal file
62
internal/ethapi/rip7560api.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -373,6 +373,22 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
|
||||||
return nil
|
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
|
// fillTransactions retrieves the pending transactions from the txpool and fills them
|
||||||
// into the given sealing block. The transaction selection and ordering strategy can
|
// into the given sealing block. The transaction selection and ordering strategy can
|
||||||
// be customized with the plugin in the future.
|
// be customized with the plugin in the future.
|
||||||
|
|
@ -411,6 +427,14 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment)
|
||||||
localBlobTxs[account] = txs
|
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.
|
// Fill the block with all available pending transactions.
|
||||||
if len(localPlainTxs) > 0 || len(localBlobTxs) > 0 {
|
if len(localPlainTxs) > 0 || len(localBlobTxs) > 0 {
|
||||||
plainTxs := newTransactionsByPriceAndNonce(env.signer, localPlainTxs, env.header.BaseFee)
|
plainTxs := newTransactionsByPriceAndNonce(env.signer, localPlainTxs, env.header.BaseFee)
|
||||||
|
|
|
||||||
|
|
@ -368,6 +368,11 @@ type ChainConfig struct {
|
||||||
// Various consensus engines
|
// Various consensus engines
|
||||||
Ethash *EthashConfig `json:"ethash,omitempty"`
|
Ethash *EthashConfig `json:"ethash,omitempty"`
|
||||||
Clique *CliqueConfig `json:"clique,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.
|
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue