feat(core): implemented signed transaction RLP decoding (#1096)
* feat(core): implement signed transaction decoding * add geth signed transaction test vectors * add signed tx decoding CHANGELOG entry
This commit is contained in:
parent
247f08f1a9
commit
6e004e7780
|
@ -6,6 +6,7 @@
|
|||
|
||||
- Pass compilation time as additional argument to `Reporter::on_solc_success` [1098](https://github.com/gakonst/ethers-rs/pull/1098)
|
||||
- Fix aws signer bug which maps un-normalized signature to error if no normalization occurs (in `aws::utils::decode_signature`)
|
||||
- Implement signed transaction RLP decoding [#1096](https://github.com/gakonst/ethers-rs/pull/1096)
|
||||
- `Transaction::from` will default to `Address::zero()`. Add `recover_from` and
|
||||
`recover_from_mut` methods for recovering the sender from signature, and also
|
||||
setting the same on tx [1075](https://github.com/gakonst/ethers-rs/pull/1075).
|
||||
|
|
|
@ -10,7 +10,7 @@ repository = "https://github.com/gakonst/ethers-rs"
|
|||
keywords = ["ethereum", "web3", "celo", "ethers"]
|
||||
|
||||
[dependencies]
|
||||
rlp = { version = "0.5.0", default-features = false }
|
||||
rlp = { version = "0.5.0", default-features = false, features = ["std"] }
|
||||
ethabi = { version = "17.0.0", default-features = false, features = ["full-serde", "rlp"] }
|
||||
arrayvec = { version = "0.7.2", default-features = false }
|
||||
rlp-derive = { version = "0.1.0", default-features = false }
|
||||
|
|
|
@ -1,14 +1,29 @@
|
|||
use super::{decode_to, eip2930::AccessList, normalize_v, rlp_opt};
|
||||
use crate::{
|
||||
types::{Address, Bytes, NameOrAddress, Signature, Transaction, H256, U256, U64},
|
||||
types::{
|
||||
Address, Bytes, NameOrAddress, Signature, SignatureError, Transaction, H256, U256, U64,
|
||||
},
|
||||
utils::keccak256,
|
||||
};
|
||||
use rlp::{Decodable, DecoderError, RlpStream};
|
||||
use thiserror::Error;
|
||||
|
||||
/// EIP-1559 transactions have 9 fields
|
||||
const NUM_TX_FIELDS: usize = 9;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An error involving an EIP1559 transaction request.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Eip1559RequestError {
|
||||
/// When decoding a transaction request from RLP
|
||||
#[error(transparent)]
|
||||
DecodingError(#[from] rlp::DecoderError),
|
||||
/// When recovering the address from a signature
|
||||
#[error(transparent)]
|
||||
RecoveryError(#[from] SignatureError),
|
||||
}
|
||||
|
||||
/// Parameters for sending a transaction
|
||||
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||
pub struct Eip1559TransactionRequest {
|
||||
|
@ -188,38 +203,53 @@ impl Eip1559TransactionRequest {
|
|||
/// Decodes fields of the request starting at the RLP offset passed. Increments the offset for
|
||||
/// each element parsed.
|
||||
#[inline]
|
||||
fn decode_base_rlp(&mut self, rlp: &rlp::Rlp, offset: &mut usize) -> Result<(), DecoderError> {
|
||||
self.chain_id = Some(rlp.val_at(*offset)?);
|
||||
pub fn decode_base_rlp(rlp: &rlp::Rlp, offset: &mut usize) -> Result<Self, DecoderError> {
|
||||
let mut tx = Self::new();
|
||||
tx.chain_id = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.nonce = Some(rlp.val_at(*offset)?);
|
||||
tx.nonce = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.max_priority_fee_per_gas = Some(rlp.val_at(*offset)?);
|
||||
tx.max_priority_fee_per_gas = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.max_fee_per_gas = Some(rlp.val_at(*offset)?);
|
||||
tx.max_fee_per_gas = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.gas = Some(rlp.val_at(*offset)?);
|
||||
tx.gas = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.to = decode_to(rlp, offset)?;
|
||||
self.value = Some(rlp.val_at(*offset)?);
|
||||
tx.to = decode_to(rlp, offset)?;
|
||||
tx.value = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
let data = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
|
||||
self.data = match data.len() {
|
||||
tx.data = match data.len() {
|
||||
0 => None,
|
||||
_ => Some(Bytes::from(data.to_vec())),
|
||||
};
|
||||
*offset += 1;
|
||||
self.access_list = rlp.val_at(*offset)?;
|
||||
tx.access_list = rlp.val_at(*offset)?;
|
||||
*offset += 1;
|
||||
Ok(())
|
||||
Ok(tx)
|
||||
}
|
||||
|
||||
/// Decodes the given RLP into a transaction, attempting to decode its signature as well.
|
||||
pub fn decode_signed_rlp(rlp: &rlp::Rlp) -> Result<(Self, Signature), Eip1559RequestError> {
|
||||
let mut offset = 0;
|
||||
let mut txn = Self::decode_base_rlp(rlp, &mut offset)?;
|
||||
|
||||
let v = rlp.at(offset)?.as_val()?;
|
||||
offset += 1;
|
||||
let r = rlp.at(offset)?.as_val()?;
|
||||
offset += 1;
|
||||
let s = rlp.at(offset)?.as_val()?;
|
||||
|
||||
let sig = Signature { r, s, v };
|
||||
txn.from = Some(sig.recover(txn.sighash())?);
|
||||
|
||||
Ok((txn, sig))
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Eip1559TransactionRequest {
|
||||
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
|
||||
let mut txn = Eip1559TransactionRequest::new();
|
||||
let mut offset = 0;
|
||||
txn.decode_base_rlp(rlp, &mut offset)?;
|
||||
Ok(txn)
|
||||
Self::decode_base_rlp(rlp, &mut 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::{
|
||||
eip1559::Eip1559TransactionRequest,
|
||||
eip1559::{Eip1559RequestError, Eip1559TransactionRequest},
|
||||
eip2930::{AccessList, Eip2930TransactionRequest},
|
||||
request::RequestError,
|
||||
};
|
||||
use crate::{
|
||||
types::{
|
||||
|
@ -8,7 +9,9 @@ use crate::{
|
|||
},
|
||||
utils::keccak256,
|
||||
};
|
||||
use rlp::Decodable;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
/// The TypedTransaction enum represents all Ethereum transaction types.
|
||||
///
|
||||
|
@ -36,6 +39,26 @@ pub enum TypedTransaction {
|
|||
Eip1559(Eip1559TransactionRequest),
|
||||
}
|
||||
|
||||
/// An error involving a typed transaction request.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TypedTransactionError {
|
||||
/// When decoding a signed legacy transaction
|
||||
#[error(transparent)]
|
||||
LegacyError(#[from] RequestError),
|
||||
/// When decoding a signed Eip1559 transaction
|
||||
#[error(transparent)]
|
||||
Eip1559Error(#[from] Eip1559RequestError),
|
||||
/// Error decoding the transaction type from the transaction's RLP encoding
|
||||
#[error(transparent)]
|
||||
TypeDecodingError(#[from] rlp::DecoderError),
|
||||
/// Missing transaction type when decoding from RLP
|
||||
#[error("Missing transaction type when decoding")]
|
||||
MissingTransactionType,
|
||||
/// Missing transaction payload when decoding from RLP
|
||||
#[error("Missing transaction payload when decoding")]
|
||||
MissingTransactionPayload,
|
||||
}
|
||||
|
||||
#[cfg(feature = "legacy")]
|
||||
impl Default for TypedTransaction {
|
||||
fn default() -> Self {
|
||||
|
@ -265,15 +288,51 @@ impl TypedTransaction {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Hashes the transaction's data with the included signature.
|
||||
pub fn hash(&self, signature: &Signature) -> H256 {
|
||||
keccak256(&self.rlp_signed(signature).as_ref()).into()
|
||||
}
|
||||
|
||||
/// Get a TypedTransaction directly from an rlp encoded byte stream
|
||||
impl rlp::Decodable for TypedTransaction {
|
||||
/// Decodes a signed TypedTransaction from a rlp encoded byte stream
|
||||
pub fn decode_signed(rlp: &rlp::Rlp) -> Result<(Self, Signature), TypedTransactionError> {
|
||||
let tx_type: Option<U64> = match rlp.is_data() {
|
||||
true => Ok(Some(rlp.data()?.into())),
|
||||
false => Err(TypedTransactionError::MissingTransactionType),
|
||||
}?;
|
||||
|
||||
let rest = rlp::Rlp::new(
|
||||
rlp.as_raw().get(1..).ok_or(TypedTransactionError::MissingTransactionPayload)?,
|
||||
);
|
||||
|
||||
match tx_type {
|
||||
Some(x) if x == U64::from(1u64) => {
|
||||
// EIP-2930 (0x01)
|
||||
let decoded_request = Eip2930TransactionRequest::decode_signed_rlp(&rest)?;
|
||||
Ok((Self::Eip2930(decoded_request.0), decoded_request.1))
|
||||
}
|
||||
Some(x) if x == U64::from(2u64) => {
|
||||
// EIP-1559 (0x02)
|
||||
let decoded_request = Eip1559TransactionRequest::decode_signed_rlp(&rest)?;
|
||||
Ok((Self::Eip1559(decoded_request.0), decoded_request.1))
|
||||
}
|
||||
_ => {
|
||||
// Legacy (0x00)
|
||||
// use the original rlp
|
||||
let decoded_request = TransactionRequest::decode_signed_rlp(&rest)?;
|
||||
Ok((Self::Legacy(decoded_request.0), decoded_request.1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a TypedTransaction directly from a rlp encoded byte stream
|
||||
impl Decodable for TypedTransaction {
|
||||
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
|
||||
let tx_type: Option<U64> = match rlp.is_data() {
|
||||
true => Some(rlp.data().unwrap().into()),
|
||||
false => None,
|
||||
};
|
||||
true => Ok(Some(rlp.data()?.into())),
|
||||
false => Ok(None),
|
||||
}?;
|
||||
let rest = rlp::Rlp::new(
|
||||
rlp.as_raw().get(1..).ok_or(rlp::DecoderError::Custom("no transaction payload"))?,
|
||||
);
|
||||
|
@ -439,6 +498,37 @@ mod tests {
|
|||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signed_tx_decode() {
|
||||
let expected_tx = Eip1559TransactionRequest::new()
|
||||
.from(Address::from_str("0x27519a1d088898e04b12f9fb9733267a5e61481e").unwrap())
|
||||
.chain_id(1u64)
|
||||
.nonce(0u64)
|
||||
.max_priority_fee_per_gas(413047990155u64)
|
||||
.max_fee_per_gas(768658734568u64)
|
||||
.gas(184156u64)
|
||||
.to(Address::from_str("0x0aa7420c43b8c1a7b165d216948870c8ecfe1ee1").unwrap())
|
||||
.value(200000000000000000u64)
|
||||
.data(
|
||||
Bytes::from_str(
|
||||
"0x6ecd23060000000000000000000000000000000000000000000000000000000000000002",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let expected_envelope = TypedTransaction::Eip1559(expected_tx);
|
||||
let typed_tx_hex = hex::decode("02f899018085602b94278b85b2f7a17de88302cf5c940aa7420c43b8c1a7b165d216948870c8ecfe1ee18802c68af0bb140000a46ecd23060000000000000000000000000000000000000000000000000000000000000002c080a0c5f35bf1cc6ab13053e33b1af7400c267be17218aeadcdb4ae3eefd4795967e8a04f6871044dd6368aea8deecd1c29f55b5531020f5506502e3f79ad457051bc4a").unwrap();
|
||||
|
||||
let tx_rlp = rlp::Rlp::new(typed_tx_hex.as_slice());
|
||||
let (actual_tx, signature) = TypedTransaction::decode_signed(&tx_rlp).unwrap();
|
||||
assert_eq!(expected_envelope, actual_tx);
|
||||
assert_eq!(
|
||||
expected_envelope.hash(&signature),
|
||||
H256::from_str("0x206e4c71335333f8658e995cc0c4ee54395d239acb08587ab8e5409bfdd94a6f")
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "celo"))]
|
||||
#[test]
|
||||
fn test_eip155_decode() {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use super::{decode_to, normalize_v, request::TransactionRequest};
|
||||
use crate::types::{Address, Bytes, Signature, Transaction, H256, U256, U64};
|
||||
|
||||
use super::{extract_chain_id, normalize_v};
|
||||
use crate::types::{Address, Bytes, Signature, Transaction, TransactionRequest, H256, U256, U64};
|
||||
use rlp::{Decodable, DecoderError, RlpStream};
|
||||
use rlp_derive::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -104,42 +103,37 @@ impl Eip2930TransactionRequest {
|
|||
rlp.out().freeze().into()
|
||||
}
|
||||
|
||||
/// Decodes fields based on the RLP offset passed
|
||||
fn decode_base_rlp(&mut self, rlp: &rlp::Rlp, offset: &mut usize) -> Result<(), DecoderError> {
|
||||
self.tx.chain_id = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.tx.nonce = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.tx.gas_price = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.tx.gas = Some(rlp.val_at(*offset)?);
|
||||
/// Decodes fields based on the RLP offset passed.
|
||||
fn decode_base_rlp(rlp: &rlp::Rlp, offset: &mut usize) -> Result<Self, DecoderError> {
|
||||
let request = TransactionRequest::decode_unsigned_rlp_base(rlp, offset)?;
|
||||
let access_list = rlp.val_at(*offset)?;
|
||||
*offset += 1;
|
||||
|
||||
self.tx.gas = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
self.tx.to = decode_to(rlp, offset)?;
|
||||
self.tx.value = Some(rlp.val_at(*offset)?);
|
||||
*offset += 1;
|
||||
let data = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
|
||||
self.tx.data = match data.len() {
|
||||
0 => None,
|
||||
_ => Some(Bytes::from(data.to_vec())),
|
||||
};
|
||||
*offset += 1;
|
||||
self.access_list = rlp.val_at(*offset)?;
|
||||
*offset += 1;
|
||||
Ok(Self { tx: request, access_list })
|
||||
}
|
||||
|
||||
Ok(())
|
||||
/// Decodes the given RLP into a transaction, attempting to decode its signature as well.
|
||||
pub fn decode_signed_rlp(rlp: &rlp::Rlp) -> Result<(Self, Signature), rlp::DecoderError> {
|
||||
let mut offset = 0;
|
||||
let mut txn = Self::decode_base_rlp(rlp, &mut offset)?;
|
||||
|
||||
let v = rlp.at(offset)?.as_val()?;
|
||||
// populate chainid from v
|
||||
txn.tx.chain_id = extract_chain_id(v);
|
||||
offset += 1;
|
||||
let r = rlp.at(offset)?.as_val()?;
|
||||
offset += 1;
|
||||
let s = rlp.at(offset)?.as_val()?;
|
||||
|
||||
let sig = Signature { r, s, v };
|
||||
Ok((txn, sig))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a Eip2930TransactionRequest from a rlp encoded byte stream
|
||||
impl Decodable for Eip2930TransactionRequest {
|
||||
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
|
||||
let mut new_tx = Self::new(TransactionRequest::new(), AccessList::default());
|
||||
let mut offset = 0;
|
||||
new_tx.decode_base_rlp(rlp, &mut offset)?;
|
||||
Ok(new_tx)
|
||||
Self::decode_base_rlp(rlp, &mut 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
//! Transaction types
|
||||
use super::{decode_to, extract_chain_id, rlp_opt, NUM_TX_FIELDS};
|
||||
use crate::{
|
||||
types::{Address, Bytes, NameOrAddress, Signature, Transaction, H256, U256, U64},
|
||||
types::{
|
||||
Address, Bytes, NameOrAddress, Signature, SignatureError, Transaction, H256, U256, U64,
|
||||
},
|
||||
utils::keccak256,
|
||||
};
|
||||
|
||||
use rlp::{Decodable, RlpStream};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
/// An error involving a transaction request.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RequestError {
|
||||
/// When decoding a transaction request from RLP
|
||||
#[error(transparent)]
|
||||
DecodingError(#[from] rlp::DecoderError),
|
||||
/// When recovering the address from a signature
|
||||
#[error(transparent)]
|
||||
RecoveryError(#[from] SignatureError),
|
||||
}
|
||||
|
||||
/// Parameters for sending a transaction
|
||||
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||
|
@ -195,7 +209,7 @@ impl TransactionRequest {
|
|||
|
||||
/// Decodes the unsigned rlp, returning the transaction request and incrementing the counter
|
||||
/// passed as we are traversing the rlp list.
|
||||
fn decode_unsigned_rlp_base(
|
||||
pub(crate) fn decode_unsigned_rlp_base(
|
||||
rlp: &rlp::Rlp,
|
||||
offset: &mut usize,
|
||||
) -> Result<Self, rlp::DecoderError> {
|
||||
|
@ -249,12 +263,12 @@ impl TransactionRequest {
|
|||
}
|
||||
|
||||
/// Decodes the given RLP into a transaction, attempting to decode its signature as well.
|
||||
pub fn decode_signed_rlp(rlp: &rlp::Rlp) -> Result<(Self, Signature), rlp::DecoderError> {
|
||||
pub fn decode_signed_rlp(rlp: &rlp::Rlp) -> Result<(Self, Signature), RequestError> {
|
||||
let mut offset = 0;
|
||||
let mut txn = Self::decode_unsigned_rlp_base(rlp, &mut offset)?;
|
||||
|
||||
let v = rlp.at(offset)?.as_val()?;
|
||||
// populate chainid from v
|
||||
// populate chainid from v in case the signature follows EIP155
|
||||
txn.chain_id = extract_chain_id(v);
|
||||
offset += 1;
|
||||
let r = rlp.at(offset)?.as_val()?;
|
||||
|
@ -262,6 +276,8 @@ impl TransactionRequest {
|
|||
let s = rlp.at(offset)?.as_val()?;
|
||||
|
||||
let sig = Signature { r, s, v };
|
||||
txn.from = Some(sig.recover(txn.sighash())?);
|
||||
|
||||
Ok((txn, sig))
|
||||
}
|
||||
}
|
||||
|
@ -269,7 +285,7 @@ impl TransactionRequest {
|
|||
impl Decodable for TransactionRequest {
|
||||
/// Decodes the given RLP into a transaction request, ignoring the signature if populated
|
||||
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
|
||||
TransactionRequest::decode_unsigned_rlp(rlp)
|
||||
Self::decode_unsigned_rlp(rlp)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,10 +351,11 @@ impl TransactionRequest {
|
|||
#[cfg(test)]
|
||||
#[cfg(not(feature = "celo"))]
|
||||
mod tests {
|
||||
use crate::types::Signature;
|
||||
use crate::types::{NameOrAddress, Signature};
|
||||
use rlp::{Decodable, Rlp};
|
||||
|
||||
use super::{Address, TransactionRequest, U256, U64};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn encode_decode_rlp() {
|
||||
|
@ -469,4 +486,54 @@ mod tests {
|
|||
assert_eq!(expected_sig, decoded_sig);
|
||||
assert_eq!(decoded_tx.chain_id, Some(U64::from(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eip155_signing_decode_vitalik() {
|
||||
// Test vectors come from http://vitalik.ca/files/eip155_testvec.txt and
|
||||
// https://github.com/ethereum/go-ethereum/blob/master/core/types/transaction_signing_test.go
|
||||
// Tests that the rlp decoding properly extracts the from address
|
||||
let rlp_transactions =
|
||||
vec!["f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d",
|
||||
"f864018504a817c80182a410943535353535353535353535353535353535353535018025a0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bcaa0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6",
|
||||
"f864028504a817c80282f618943535353535353535353535353535353535353535088025a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5",
|
||||
"f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de",
|
||||
"f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060",
|
||||
"f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1",
|
||||
"f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2fa06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d",
|
||||
"f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021",
|
||||
"f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
|
||||
"f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"];
|
||||
let rlp_transactions_bytes = rlp_transactions
|
||||
.iter()
|
||||
.map(|rlp_str| hex::decode(rlp_str).unwrap())
|
||||
.collect::<Vec<Vec<u8>>>();
|
||||
|
||||
let raw_addresses = vec![
|
||||
"0xf0f6f18bca1b28cd68e4357452947e021241e9ce",
|
||||
"0x23ef145a395ea3fa3deb533b8a9e1b4c6c25d112",
|
||||
"0x2e485e0c23b4c3c542628a5f672eeab0ad4888be",
|
||||
"0x82a88539669a3fd524d669e858935de5e5410cf0",
|
||||
"0xf9358f2538fd5ccfeb848b64a96b743fcc930554",
|
||||
"0xa8f7aba377317440bc5b26198a363ad22af1f3a4",
|
||||
"0xf1f571dc362a0e5b2696b8e775f8491d3e50de35",
|
||||
"0xd37922162ab7cea97c97a87551ed02c9a38b7332",
|
||||
"0x9bddad43f934d313c2b79ca28a432dd2b7281029",
|
||||
"0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f",
|
||||
];
|
||||
|
||||
let addresses = raw_addresses
|
||||
.iter()
|
||||
.map(|addr| NameOrAddress::Address(Address::from_str(*addr).unwrap()));
|
||||
|
||||
// decoding will do sender recovery and we don't expect any of these to error, so we should
|
||||
// check that the address matches for each decoded transaction
|
||||
let decoded_transactions = rlp_transactions_bytes.iter().map(|raw_tx| {
|
||||
TransactionRequest::decode_signed_rlp(&Rlp::new(raw_tx.as_slice())).unwrap().0
|
||||
});
|
||||
|
||||
for (tx, from_addr) in decoded_transactions.zip(addresses) {
|
||||
let from_tx: NameOrAddress = tx.from.unwrap().into();
|
||||
assert_eq!(from_tx, from_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -330,13 +330,16 @@ impl Transaction {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get a TransactionReceipt directly from an rlp encoded byte stream
|
||||
/// Get a Transaction directly from a rlp encoded byte stream
|
||||
impl Decodable for Transaction {
|
||||
fn decode(rlp: &rlp::Rlp) -> Result<Self, DecoderError> {
|
||||
let mut txn = Self::default();
|
||||
// we can get the type from the first value
|
||||
let mut offset = 0;
|
||||
txn.transaction_type = Some(rlp.data().unwrap().into());
|
||||
txn.transaction_type = match rlp.is_data() {
|
||||
true => Ok(Some(rlp.data()?.into())),
|
||||
false => Ok(None),
|
||||
}?;
|
||||
let rest = rlp::Rlp::new(
|
||||
rlp.as_raw().get(1..).ok_or(DecoderError::Custom("no transaction payload"))?,
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue