Skip to content

Embed TxEnvelope into rpc-types-eth::Transaction#1460

Merged
klkvr merged 25 commits intomainfrom
prestwich/tx-embed
Oct 31, 2024
Merged

Embed TxEnvelope into rpc-types-eth::Transaction#1460
klkvr merged 25 commits intomainfrom
prestwich/tx-embed

Conversation

@prestwich
Copy link
Copy Markdown
Member

@prestwich prestwich commented Oct 13, 2024

closes #1165. Alternative smaller approach than #1169

This is a breaking change

cc @klkvr

Solution

changes:

  • Embed T in alloy_rpc_types_eth::Transaction, default to TxEnvelope
  • Modify implementation of TransactionTrait for Transaction<T>
  • delete rpc_types_eth::Signature as in [wip] feat: embed TxEnvelope into rpc_types::Transaction #1169
  • delete tests that tested invalid responses, as those are no longer deserialized
  • introduce AnyTxEnvelope, AnyTypedTransaction and AnyReceiptEnvelope abstractions
  • add TransactionResponse: AsRef<Self::TxEnvelope> bound to network assoc type

drive-by

  • mark tokio as a non-wasm dependency to silence warnings in cargo hack --target wasm32-unknown-unknown

is this correct?

  • remove legacy gasPrice field from TxPoolContent. My belief is that this is now only produced for legacy transactions, so removing it is correct behavior and will not break deserializers

PR Checklist

  • Added Tests
  • Added Documentation
  • Breaking changes

@klkvr
Copy link
Copy Markdown
Member

klkvr commented Oct 13, 2024

this would break AnyNetwork because rpc_types_eth::Transaction would not be able to deserialize non-ethereum transaction types

@prestwich
Copy link
Copy Markdown
Member Author

prestwich commented Oct 13, 2024

this would break AnyNetwork because rpc_types_eth::Transaction would not be able to deserialize non-ethereum transaction types

<AnyNetwork as Network>::TxEnvelope is already set to TxEnvelope, which means the provider already cannot correctly sign or send non-ethereum transactions. So this would not break it any worse than it is already broken. It would make the type somewhat more internally consistent too

@klkvr
Copy link
Copy Markdown
Member

klkvr commented Oct 13, 2024

this would break AnyNetwork because rpc_types_eth::Transaction would not be able to deserialize non-ethereum transaction types

<AnyNetwork as Network>::TxEnvelope is already set to TxEnvelope so this would not break it any worse than it is already broken. It would make the type somewhat more internally consistent too

TransactionResponse is still an rpc type. changing transaction respose type to an envelope would break eg get_block(..., true) requests deserialization on OP because of deposit transaction

@prestwich
Copy link
Copy Markdown
Member Author

current CI error seems to indicate that anvil sometimes doesn't return a type flag?

"{\"hash\":\"0x4b56f1a6bdceb76d1b843e978c70ab88e38aa19f1a67be851b10ce4eec65b7d4\",\"nonce\":\"0x0\",\"blockHash\":\"0xc3d639df51a3a7af3c0587b16d4a59c66bb8c2724d288fca3d3d0b3a505689e0\",\"blockNumber\":\"0x1\",\"transactionIndex\":\"0x0\",\"from\":\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\",\"to\":\"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\",\"value\":\"0x64\",\"gasPrice\":\"0x4a817c800\",\"gas\":\"0x5208\",\"input\":\"0x\",\"r\":\"0x71575df226771c8a9c18eab02457d65bc02ab1b978c78ec4b291f903305291b6\",\"s\":\"0xd04224f8696f449af09a1851bedcaeae3c5836cedb8ccbe489622a34278b0da\",\"v\":\"0xf4f6\",\"chainId\":\"0x7a69\"}"

@prestwich
Copy link
Copy Markdown
Member Author

prestwich commented Oct 13, 2024

TransactionResponse is still an rpc type. changing transaction respose type to an envelope would break eg get_block(..., true) requests deserialization on OP because of deposit transaction

yes, we agree that this is a breaking change. however, it is not going to break the AnyNetwork type, as it is already broken

a more-complete fix might look like this:

#[serde(untagged)]
pub enum AnyTxEnvelope { 
    Known(TxEnvelope), 
    Unknown { type: u8, fields: HashMap<String, Value> } 
}

but that's somewhat outside the scope of #1165

@klkvr
Copy link
Copy Markdown
Member

klkvr commented Oct 13, 2024

current CI error seems to indicate that anvil sometimes doesn't return a type flag?

"{\"hash\":\"0x4b56f1a6bdceb76d1b843e978c70ab88e38aa19f1a67be851b10ce4eec65b7d4\",\"nonce\":\"0x0\",\"blockHash\":\"0xc3d639df51a3a7af3c0587b16d4a59c66bb8c2724d288fca3d3d0b3a505689e0\",\"blockNumber\":\"0x1\",\"transactionIndex\":\"0x0\",\"from\":\"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266\",\"to\":\"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045\",\"value\":\"0x64\",\"gasPrice\":\"0x4a817c800\",\"gas\":\"0x5208\",\"input\":\"0x\",\"r\":\"0x71575df226771c8a9c18eab02457d65bc02ab1b978c78ec4b291f903305291b6\",\"s\":\"0xd04224f8696f449af09a1851bedcaeae3c5836cedb8ccbe489622a34278b0da\",\"v\":\"0xf4f6\",\"chainId\":\"0x7a69\"}"

yep, anvil does not return type flag for legacy transactions atm, I think we should fix this though I believe this should be handled during deserialization as well

@klkvr
Copy link
Copy Markdown
Member

klkvr commented Oct 13, 2024

#[serde(untagged)]
pub enum AnyTxEnvelope { 
    Known(TxEnvelope), 
    Unknown { type: u8, fields: HashMap<String, Value> } 
}

this won't work because of rlp bounds on envelope

@klkvr
Copy link
Copy Markdown
Member

klkvr commented Oct 13, 2024

yes, we agree that this is a breaking change. however, it is not going to break the AnyNetwork type, as it is already broken

right now we are using AnyNetwork in foundry to fetch and display/re-execute arbitrary transactions through AnyNetwork. including this PR as is would break this

@klkvr
Copy link
Copy Markdown
Member

klkvr commented Oct 13, 2024

it would be great if we'd figure out a way to have TransactionResponse: Into<Self::TxEnvelope> bound on Network but this is just not possible if we want to have a catch-all network implementation (which AnyNetwork is supposed to be)

@prestwich
Copy link
Copy Markdown
Member Author

this won't work because of rlp bounds on envelope

Envelope doesn't have RLP bounds, only 2718 bounds, which can be satisfied with a dummy implementation. It's impossible to correctly model unknown 2718 encodings so incorrect behavior here should be acceptable

@prestwich
Copy link
Copy Markdown
Member Author

prestwich commented Oct 13, 2024

latest commit includes AnyTxEnvelope and AnyTypedTransaction, as well as workarounds for allowing them to meet the transaction trait by memoizing desered values

@prestwich
Copy link
Copy Markdown
Member Author

okay @klkvr this should be ready for re-review with above concerns addressed. I also put a summary of changes in the PR description

this would break AnyNetwork because rpc_types_eth::Transaction would not be able to deserialize non-ethereum transaction types

now handled by the new Any_____ types

yep, anvil does not return type flag for legacy transactions atm, I think we should fix this though I believe this should be handled during deserialization as well

handled by MaybeTagged deser

this won't work because of rlp bounds on envelope

handled by the dummy 2718 impls

it would be great if we'd figure out a way to have TransactionResponse: IntoSelf::TxEnvelope bound on Network but this is just not possible if we want to have a catch-all network implementation (which AnyNetwork is supposed to be)

I added this as AsRef<Self::TxEnvelope>. An Into bound is blocked by WithOtherFields, as we can't generically impl<T, U> Into<U> for WithOtherFields where T: Into<U> because it conflicts with std lib impls

@klkvr
Copy link
Copy Markdown
Member

klkvr commented Oct 13, 2024

nice! thanks for spending your time on this

handled by the dummy 2718 impls

this is kind of hacky but I don't think there's a way to fit AnyNetwork into current abstraction without this hack or another one. wdyt @mattsse @emhane

@prestwich
Copy link
Copy Markdown
Member Author

also, agree the 2718 is pretty jank >.> i thought about making it panic instead

@klkvr
Copy link
Copy Markdown
Member

klkvr commented Oct 14, 2024

also, agree the 2718 is pretty jank >.> i thought about making it panic instead

something we could do is divide Network into 2 traits — one which would just contain the ATs we need for provider without much bounds (basically we just need serde for responses and 2718 for tx envelope). could call it ProviderNetwork

and second trait which would represent a normal network with infallible rpc<->consensus conversions which would have a blanket impl for all ProviderNetwork implementations satisfying stronger bounds. AnyNetwork would just not implement it

however doing all of this just for AnyNetwork doesn't make much sense

@prestwich
Copy link
Copy Markdown
Member Author

something we could do is divide Network into 2 traits — one which would just contain the ATs we need for provider without much bounds (basically we just need serde for responses and 2718 for tx envelope). could call it ProviderNetwork

and second trait which would represent a normal network with infallible rpc<->consensus conversions which would have a blanket impl for all ProviderNetwork implementations satisfying stronger bounds. AnyNetwork would just not implement it

however doing all of this just for AnyNetwork doesn't make much sense

I like the idea generally, but i think you're right that doing it just for AnyNetwork doesn't make much sense. It would introduce a lot of mental overhead for bounds-writing, which is common, while using AnyNetwork is somewhat uncommon

@prestwich
Copy link
Copy Markdown
Member Author

updated the docs for AnyNetwork to reflect some known rough edges :)

#[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
pub authorization_list: Option<Vec<SignedAuthorization>>,
// /// Transaction hash
// pub hash: B256,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is the last outstanding issue. For some reason, uncommenting this makes inner deserialization fail. Probably a subtle and annoying serde interaction 😮‍💨

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ah, it's because

  • Signed has an unskipped hash field
  • the outer Transaction consumes the "hash" in deserialization
  • there is no "hash" field left for Signed to deserialize

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

so the problem becomes

  • let Signed deserialize the hash
  • make hash available on Transaction in a generic way

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

OR

  • manually impl deserialize for Transaction to avoid consuming the hash

this seems easier and doesn't require introducing new bounds 👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

should we just remove the hash from Transaction?

Copy link
Copy Markdown
Member Author

@prestwich prestwich Oct 15, 2024

Choose a reason for hiding this comment

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

I believe transaction hash is just keccak256(tx_envelope.encode_2718())? which should be possible to do for any envelope

will look into this today. at first blush, it looks like trie_hash is provided on the Encodable2718 trait, so we should be able to remove from Signed. We still strongly want memoization tho so need to figure out how to keep that without interfering with serialization 🤔

it also looks like d0794e5 lightly broke eip2718 abstraction by making type flag encoding tx-associated instead of envelope-associated. We would now behave badly for networks where the type flag is different (e.g. TxEip1559 is flag 17, or where both 4 and 5 are TxEip7702, so we should probably open a separate issue for that

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I believe transaction hash is just keccak256(tx_envelope.encode_2718())? which should be possible to do for any envelope

this is actually not the case for eip4844 with sidecar 😮‍💨 but it's straightforward to work around i think

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm wondering if we could solve this by adding a TxEnvelope trait which would have a tx_hash method? all normal variants already keep hash in Signed and we can just add a mandatory hash field to Other variant

I think #1485 is reasonable as well because sealable and signable are 2 valid distinct properties of transaction (eg deposit transaction is only sealable). but would like to keep scope here smaller if possible as PR is already pretty big

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm wondering if we could solve this by adding a TxEnvelope trait which would have a tx_hash method? all normal variants already keep hash in Signed and we can just add a mandatory hash field to Other variant

the trie_hash method already exists on Encodable2718, so we are covered there :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the trie_hash method already exists on Encodable2718, so we are covered there :)

yeah but our impl of Encodable2718 is not correct for Other variant?

@prestwich prestwich force-pushed the prestwich/tx-embed branch 2 times, most recently from 81c92fd to 6e5065e Compare October 15, 2024 16:48
@prestwich
Copy link
Copy Markdown
Member Author

we don't have any more deserialization issues, right?

Not that I know of

This is rebased and ready for review

@prestwich
Copy link
Copy Markdown
Member Author

prestwich commented Oct 31, 2024

side note, because the type aliases for AnyRpcBlock have to reference AnyTxEnvelope they've been pushed into the alloy-network crate and re-exported from the any module. I also re-exported AnyHeader and AnyReceipt there, so all Any____ can be accessed from the same module

Comment on lines +17 to +25
pub use alloy_consensus::{AnyHeader, AnyReceiptEnvelope};

/// A catch-all header type for handling headers on multiple networks.
pub type AnyRpcHeader = alloy_rpc_types_eth::Header<alloy_consensus::AnyHeader>;

/// A catch-all block type for handling blocks on multiple networks.
pub type AnyRpcBlock =
WithOtherFields<Block<WithOtherFields<Transaction<AnyTxEnvelope>>, AnyRpcHeader>>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we already have these here

pub type AnyNetworkHeader = Header<alloy_consensus::AnyHeader>;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

#1460 (comment)

Those can't be in the rpc-types-eth crate as they need to import AnyTxEnvelope now. In addition, it's somewhat unclean to put AnyRpc____ objects into rpc-types-eth. Do you thinkwe should add rpc-types-any as a crate?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AnyRecipt and AnyHeader are already in alloy-consensus

can't AnyTxEnvelope be there too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

i don't think it was a good idea to put them there in the first place. alloy-consensus has always been specifically eth consensus. It's also much more invasive to move a large type downwards instead of moving 2 aliases upwards

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

followup work:
#1598

}
}

impl alloy_consensus::Transaction for AnyTxEnvelope {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we need this to satisfy the the Network trait constraint

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

we don't, but it seems sufficiently useful to users to have this

Copy link
Copy Markdown
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

cool, I think this is a pretty big UX improvement.

pending @klkvr

Comment on lines +15 to +16
/// A catch-all header type for handling headers on multiple networks.
pub type AnyRpcHeader = alloy_rpc_types_eth::Header<alloy_consensus::AnyHeader>;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

moving these aliases here makes sense imo

Copy link
Copy Markdown
Member

@klkvr klkvr left a comment

Choose a reason for hiding this comment

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

lgtm! will try to plug this into reth and see if anything breaks

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] trie_hash impl incorrect for tx envelope [Feature] embed consensus transaction types in eth_getTransaction

3 participants