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

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

Merged
merged 25 commits into from
Oct 31, 2024
Merged

Conversation

prestwich
Copy link
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Member Author

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

@klkvr
Copy link
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
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
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
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
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
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
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
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
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
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
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
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
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
Copy link
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
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
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
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
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
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
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

crates/network/src/any/mod.rs Outdated Show resolved Hide resolved
crates/network/src/any/mod.rs Outdated Show resolved Hide resolved
crates/network/src/any/mod.rs Outdated Show resolved Hide resolved
}
}

impl alloy_consensus::Transaction for AnyTxEnvelope {
Copy link
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
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

crates/network/src/any/mod.rs Outdated Show resolved Hide resolved
crates/network/src/any/mod.rs Show resolved Hide resolved
crates/network/src/any/mod.rs Outdated Show resolved Hide resolved
Copy link
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

crates/network/Cargo.toml Outdated Show resolved Hide resolved
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
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
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>
@klkvr klkvr enabled auto-merge (squash) October 31, 2024 23:33
@klkvr klkvr merged commit 1042998 into main Oct 31, 2024
26 checks passed
@klkvr klkvr deleted the prestwich/tx-embed branch October 31, 2024 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants