Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a new RPC endpoint (bor_sendRawTransactionConditional) to support EIP-4337 Bundled Transactions #8229

Merged
merged 22 commits into from
Dec 14, 2023

Conversation

pratikspatil024
Copy link
Contributor

@pratikspatil024 pratikspatil024 commented Sep 18, 2023

Added a new RPC endpoint (bor_sendRawTransactionConditional ) to support EIP-4337 Bundled Transactions.
Implements PIP-15.

This is still a WIP as it will require some changes in the txpool which will be made in PR#1229 in erigon-lib.

Here is the corresponding PR in bor.

case v.IsStorage():
for slot, value := range v.Storage {
slot := slot
tempByte, _ := sdb.stateReader.ReadAccountStorage(k, tempAccount.Incarnation, &slot)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't loose error plz

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated here. Thanks!

return common.Hash{}, err
}

txTemp, err := api.db.BeginRo(ctx)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in erigon we using naming:
txn - ethereum transaction
tx - databases transaction

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the information. Updated here.

}
defer txTemp.Rollback()

currentHeader := rawdb.ReadCurrentHeader(txTemp)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check nillness plz

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done here, thanks.


// this has been moved to prior to adding of transactions to capture the
// pre state of the db - which is used for logging in the messages below
tx, err := api.db.BeginRo(ctx)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • you already have temptx above
  • api.db it's chaindata db, it's not related to txpool's db. So, "pre-state" of which db do you need? I don't see any "logging below"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, my bad. Thank you, updated here.
Yes, I need the chaindata db.

return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
}

defer tx.Rollback()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, fixed here.
Thanks.

Copy link
Collaborator

@battlmonstr battlmonstr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be rebased onto devel with builtin erigon-lib

core/state/intra_block_state.go Outdated Show resolved Hide resolved
core/types/access_list_tx.go Outdated Show resolved Hide resolved
core/types/blob_tx_wrapper.go Outdated Show resolved Hide resolved
core/types/block.go Outdated Show resolved Hide resolved
core/types/block.go Outdated Show resolved Hide resolved
eth/stagedsync/stage_mining_exec.go Outdated Show resolved Hide resolved
@@ -825,3 +826,46 @@ func (sdb *IntraBlockState) AddressInAccessList(addr libcommon.Address) bool {
func (sdb *IntraBlockState) SlotInAccessList(addr libcommon.Address, slot libcommon.Hash) (addressPresent bool, slotPresent bool) {
return sdb.accessList.Contains(addr, slot)
}

// ValidateKnownAccounts validates the knownAccounts passed in the options parameter in the conditional transaction (EIP-4337)
func (sdb *IntraBlockState) ValidateKnownAccounts(knownAccounts types.KnownAccounts) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to find a better place for this method, because I feel that it doesn't belong to the IntraBlockState.
I'd also love to make it more easy to unit test by accepting StateReader interface as a parameter.

In the comment above you refer to the full EIP-4337. Could you please add a link to a subchapter of EIP-4337 where the relevant information is described?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please recommend any other place for this? The reason I added it here was because the storage slots were available at this location.

About the comment, it is not in the EIP, but in the spec released by the EF researchers here - https://notes.ethereum.org/@yoav/SkaX2lS9j.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me try to find and suggest a better place for this, and get back to you. I think it should be in the same file as ValidateTransactionConditions once we figure it out.

My current suggestion is to refactor this method to a free function by adding a parameter of type state.StateReader - you can use it to get the storage slots. The stateReader instance is accessible in SpawnMiningExecStage func, and can be passed to addTransactionsToMiningBlock as a new parameter and forwarded to ValidateTransactionConditions. It is also accessible as readerTemp in SendRawTransactionConditional func (currentState could be removed there).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd like to have a new top-level polygon folder/package for this with intention to centralize the other polygon-specific code into it in the future.
If you make a new file like polygon/transaction_conditional.go and move
ValidateKnownAccounts and ValidateTransactionConditions functions there, would it make sense to you?

core/state/intra_block_state.go Outdated Show resolved Hide resolved
rpc/types.go Outdated Show resolved Hide resolved
turbo/jsonrpc/bor_snapshot.go Outdated Show resolved Hide resolved
}

func SingleFromHex(hex string) *Value {
return &Value{Single: libcommon.HexToRefHash(hex)}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return &Value{Single: libcommon.HexToRefHash(hex)}
return &Value{Single: &libcommon.HexToHash(hex)}

with this there's no need for a new HexToRefHash function in erigon-lib

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this will not work. It gives the following error. - cannot take address of libcommon.HexToHash(hex) (value of type "github.com/ledgerwatch/erigon-lib/common".Hash)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't expect this. Maybe this is possible?

func SingleFromHex(hex string) *Value {
	hash := libcommon.HexToHash(hex)
	return &KnownAccountStorageCondition{StorageRootHash: &hash}

See also: https://stackoverflow.com/questions/10535743/address-of-a-temporary-in-go

Copy link
Collaborator

@battlmonstr battlmonstr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more general question: as a client who sends this transaction, how do I know it was rejected? How is it done with eth_sendRawTransaction? I feel in this case it is more necessary, because of unpredictable state changes. As as user I wouldn't want to sit and wait, and also not spam with more sendRawTransactionConditional to increase the chance of success.

@battlmonstr
Copy link
Collaborator

battlmonstr commented Sep 22, 2023

A more general question: as a client who sends this transaction, how do I know it was rejected? How is it done with eth_sendRawTransaction? I feel in this case it is more necessary, because of unpredictable state changes. As as user I wouldn't want to sit and wait, and also not spam with more sendRawTransactionConditional to increase the chance of success.

It is currently done using transaction replay in otterscan: https://github.com/ledgerwatch/erigon/blob/devel/turbo/jsonrpc/otterscan_transaction_error.go

We need to make sure that the new transaction validation errors are recoverable via the otterscan runTracer API call, see here: https://github.com/ledgerwatch/erigon/blob/devel/turbo/jsonrpc/otterscan_api.go#L117

@mh0lt mh0lt marked this pull request as ready for review September 28, 2023 11:05
@pratikspatil024
Copy link
Contributor Author

pratikspatil024 commented Oct 3, 2023

Hi @battlmonstr - thanks for the thorough review, I will be addressing them.

I have just rebased my branch (pratikspatil024:aa-4337) with the latest devel changes, could you also do the same with the base branch of this PR (ledgerwatch:bor_account_abstraction)? Thanks.

@pratikspatil024 pratikspatil024 marked this pull request as draft October 5, 2023 02:54
@pratikspatil024
Copy link
Contributor Author

Hi @battlmonstr

  1. as a client who sends this transaction

The bundlers are the ones sending the transaction (this transaction is known as a conditional transaction.)

  1. how do I know it was rejected? How is it done with eth_sendRawTransaction?

Yes, this transaction is treated exactly the same as the conditional transaction (only with some additional checks which validate the extra data (TransactionConditions) which is sent with the transaction). We have 2 more error codes which will be sent if the validation fails.

This is explained in detail in PIP-15 and the spec by the EF researchers.

Comment on lines +844 to +845
// check if the value is hex string or an object
switch {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like now we could move the tempAccount check and else logic outside switch before it, is it ok for you?

if tempAccount == nil {
    return fmt.Errorf("Storage Trie is nil for: %v", address)
}

// check if the value is hex string or an object
switch {
...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 558 to 564
func (tx *AccessListTx) PutOptions(options *types2.TransactionConditions) {
tx.TransactionConditions = options
}

func (tx *AccessListTx) GetOptions() *types2.TransactionConditions {
return tx.TransactionConditions
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as the logic is trivial now, do you still need these methods, or is it ok to access the field directly (and remove the methods)?

if you prefer to keep it, it should be renamed to Get/PutTransactionConditions()

Comment on lines 385 to 391
func (txw BlobTxWrapper) PutOptions(options *types2.TransactionConditions) {
txw.Tx.TransactionConditions = options
}

func (txw BlobTxWrapper) GetOptions() *types2.TransactionConditions {
return txw.Tx.TransactionConditions
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my comment about AccessListTx.PutOptions

Comment on lines 476 to 482
func (tx *DynamicFeeTransaction) PutOptions(options *types2.TransactionConditions) {
tx.TransactionConditions = options
}

func (tx *DynamicFeeTransaction) GetOptions() *types2.TransactionConditions {
return tx.TransactionConditions
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my comment about AccessListTx.PutOptions

Comment on lines 467 to 473
func (tx *LegacyTx) PutOptions(options *types2.TransactionConditions) {
tx.TransactionConditions = options
}

func (tx *LegacyTx) GetOptions() *types2.TransactionConditions {
return tx.TransactionConditions
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my comment about AccessListTx.PutOptions

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. It is good for now, I will try to suggest an alternative approach, and we'll see if we want to do it, or keep it as it is. It is on my TODO list.

eth/stagedsync/stage_mining_exec.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@battlmonstr battlmonstr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pratikspatil024 Thanks for the updates. This looks good to me apart from 2 questions that should be confirmed:

  1. Clarify how conditions propagate from the API call to the mining stage.
  2. Are those TransactionConditions in-memory only, or are they persisted in any way? If erigon process restarts, do we start from an empty txn pool or do we read it from the database? In this case do we want to recover conditions as well?
  3. Do we want the new validation errors be visible in block explorers? If yes, could we confirm that Otterscan runTracer API call can recover it? If not, what are the obstacles?

Comment on lines 296 to 297
// put options data in Tx, to use it later while block building
txn.PutOptions(&options)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do options get from here to the mining stage? isn't the txn object discarded after this call?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems to us that this property will not propagate to the mining stage

the new API call adds a transaction to the pool using the txpool GRPC Add() API:
https://github.com/ledgerwatch/interfaces/blob/master/txpool/txpool.proto#L90

it is then added to the pool in the mining process with txPool.AddLocalTxs():
https://github.com/ledgerwatch/erigon/blob/devel/erigon-lib/txpool/txpool_grpc_server.go#L216

the mining process could be a separate process from the JSON RPC API server process

later on the mining stage grabs transactions from the pool using YieldBest() here:
https://github.com/ledgerwatch/erigon/blob/devel/eth/stagedsync/stage_mining_exec.go#L202

if we want to pass TransactionConditions from the API following the same route, we'll need to extend all 3 call points (GRPC Add, AddLocalTxs, YieldBest) with extra parameters, and add a new property to TxSlot or metaTx to hold it.

@AskAlexSharov , @mh0lt , is this analysis correct? is it a reasonable thing to do, or do we want a different approach for bor-specific transaction metadata?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@battlmonstr you described well. Is this metadata encoded into txn rlp? Or it’s some side-data?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If external:

  • txpool already has MetaTx wrapper for external data (or aggregates).
  • Grpc methods: need extend.

But: txpool itself does sorting txs from best to worst - by method Less. Maybe method Less must his new fields? (It knows current block number). Then miner will have less logic.

Anyway it feels useful feature that: miner can YeldBest with conditions - it may allow build more flexible miners.

FYI: in our architecture view - miner was inside txpool process (always) - this is reason why there is no grpc between txpool and miner.

About conditions type: plz check if possible use uint256 instead of big.Int.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AskAlexSharov Thanks for the suggestions. This metadata comes as a separate parameter of this new JSON RPC call, so not inside a txn encoding.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @battlmonstr - thanks for pointing this out. I'll update this so that the options are available to the miner.

@pratikspatil024
Copy link
Contributor Author

@battlmonstr, @AskAlexSharov -we have decided to only perform the checks on the API/RPC level for now, so cleaned up the code a bit. Will add checks and continue on top of these changes later.

Can you please review this PR again? Thanks.

@pratikspatil024 pratikspatil024 marked this pull request as ready for review October 19, 2023 06:00
@pratikspatil024
Copy link
Contributor Author

Hey folks, any updates on this?

Copy link
Collaborator

@battlmonstr battlmonstr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM as a first step (see also new comments/suggestions).

@@ -827,3 +828,46 @@ func (sdb *IntraBlockState) AddressInAccessList(addr libcommon.Address) bool {
func (sdb *IntraBlockState) SlotInAccessList(addr libcommon.Address, slot libcommon.Hash) (addressPresent bool, slotPresent bool) {
return sdb.accessList.Contains(addr, slot)
}

// ValidateKnownAccounts validates the knownAccounts passed in the options parameter in the conditional transaction (EIP-4337)
func (sdb *IntraBlockState) ValidateKnownAccounts(knownAccounts types2.KnownAccountStorageConditions) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be decoupled from IntraBlockState, see this thread:
#8229 (comment)

@@ -61,6 +61,12 @@ func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) }
// If b is larger than len(h), b will be cropped from the left.
func HexToHash(s string) Hash { return BytesToHash(hexutility.FromHex(s)) }

func HexToRefHash(s string) *Hash {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to match the method above:

Suggested change
func HexToRefHash(s string) *Hash {
func HexToHashRef(s string) *Hash {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this to the root polygon folder

@mh0lt mh0lt merged commit 1abdcf9 into ledgerwatch:bor_account_abstraction Dec 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants