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)
|
- 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`)
|
- 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
|
- `Transaction::from` will default to `Address::zero()`. Add `recover_from` and
|
||||||
`recover_from_mut` methods for recovering the sender from signature, and also
|
`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).
|
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"]
|
keywords = ["ethereum", "web3", "celo", "ethers"]
|
||||||
|
|
||||||
[dependencies]
|
[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"] }
|
ethabi = { version = "17.0.0", default-features = false, features = ["full-serde", "rlp"] }
|
||||||
arrayvec = { version = "0.7.2", default-features = false }
|
arrayvec = { version = "0.7.2", default-features = false }
|
||||||
rlp-derive = { version = "0.1.0", 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 super::{decode_to, eip2930::AccessList, normalize_v, rlp_opt};
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{Address, Bytes, NameOrAddress, Signature, Transaction, H256, U256, U64},
|
types::{
|
||||||
|
Address, Bytes, NameOrAddress, Signature, SignatureError, Transaction, H256, U256, U64,
|
||||||
|
},
|
||||||
utils::keccak256,
|
utils::keccak256,
|
||||||
};
|
};
|
||||||
use rlp::{Decodable, DecoderError, RlpStream};
|
use rlp::{Decodable, DecoderError, RlpStream};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
/// EIP-1559 transactions have 9 fields
|
/// EIP-1559 transactions have 9 fields
|
||||||
const NUM_TX_FIELDS: usize = 9;
|
const NUM_TX_FIELDS: usize = 9;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
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
|
/// Parameters for sending a transaction
|
||||||
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
||||||
pub struct Eip1559TransactionRequest {
|
pub struct Eip1559TransactionRequest {
|
||||||
|
@ -188,38 +203,53 @@ impl Eip1559TransactionRequest {
|
||||||
/// Decodes fields of the request starting at the RLP offset passed. Increments the offset for
|
/// Decodes fields of the request starting at the RLP offset passed. Increments the offset for
|
||||||
/// each element parsed.
|
/// each element parsed.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn decode_base_rlp(&mut self, rlp: &rlp::Rlp, offset: &mut usize) -> Result<(), DecoderError> {
|
pub fn decode_base_rlp(rlp: &rlp::Rlp, offset: &mut usize) -> Result<Self, DecoderError> {
|
||||||
self.chain_id = Some(rlp.val_at(*offset)?);
|
let mut tx = Self::new();
|
||||||
|
tx.chain_id = Some(rlp.val_at(*offset)?);
|
||||||
*offset += 1;
|
*offset += 1;
|
||||||
self.nonce = Some(rlp.val_at(*offset)?);
|
tx.nonce = Some(rlp.val_at(*offset)?);
|
||||||
*offset += 1;
|
*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;
|
*offset += 1;
|
||||||
self.max_fee_per_gas = Some(rlp.val_at(*offset)?);
|
tx.max_fee_per_gas = Some(rlp.val_at(*offset)?);
|
||||||
*offset += 1;
|
*offset += 1;
|
||||||
self.gas = Some(rlp.val_at(*offset)?);
|
tx.gas = Some(rlp.val_at(*offset)?);
|
||||||
*offset += 1;
|
*offset += 1;
|
||||||
self.to = decode_to(rlp, offset)?;
|
tx.to = decode_to(rlp, offset)?;
|
||||||
self.value = Some(rlp.val_at(*offset)?);
|
tx.value = Some(rlp.val_at(*offset)?);
|
||||||
*offset += 1;
|
*offset += 1;
|
||||||
let data = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
|
let data = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
|
||||||
self.data = match data.len() {
|
tx.data = match data.len() {
|
||||||
0 => None,
|
0 => None,
|
||||||
_ => Some(Bytes::from(data.to_vec())),
|
_ => Some(Bytes::from(data.to_vec())),
|
||||||
};
|
};
|
||||||
*offset += 1;
|
*offset += 1;
|
||||||
self.access_list = rlp.val_at(*offset)?;
|
tx.access_list = rlp.val_at(*offset)?;
|
||||||
*offset += 1;
|
*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 {
|
impl Decodable for Eip1559TransactionRequest {
|
||||||
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
|
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
|
||||||
let mut txn = Eip1559TransactionRequest::new();
|
Self::decode_base_rlp(rlp, &mut 0)
|
||||||
let mut offset = 0;
|
|
||||||
txn.decode_base_rlp(rlp, &mut offset)?;
|
|
||||||
Ok(txn)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::{
|
use super::{
|
||||||
eip1559::Eip1559TransactionRequest,
|
eip1559::{Eip1559RequestError, Eip1559TransactionRequest},
|
||||||
eip2930::{AccessList, Eip2930TransactionRequest},
|
eip2930::{AccessList, Eip2930TransactionRequest},
|
||||||
|
request::RequestError,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{
|
types::{
|
||||||
|
@ -8,7 +9,9 @@ use crate::{
|
||||||
},
|
},
|
||||||
utils::keccak256,
|
utils::keccak256,
|
||||||
};
|
};
|
||||||
|
use rlp::Decodable;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
/// The TypedTransaction enum represents all Ethereum transaction types.
|
/// The TypedTransaction enum represents all Ethereum transaction types.
|
||||||
///
|
///
|
||||||
|
@ -36,6 +39,26 @@ pub enum TypedTransaction {
|
||||||
Eip1559(Eip1559TransactionRequest),
|
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")]
|
#[cfg(feature = "legacy")]
|
||||||
impl Default for TypedTransaction {
|
impl Default for TypedTransaction {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -265,15 +288,51 @@ impl TypedTransaction {
|
||||||
_ => None,
|
_ => 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
|
/// Decodes a signed TypedTransaction from a rlp encoded byte stream
|
||||||
impl rlp::Decodable for TypedTransaction {
|
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> {
|
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
|
||||||
let tx_type: Option<U64> = match rlp.is_data() {
|
let tx_type: Option<U64> = match rlp.is_data() {
|
||||||
true => Some(rlp.data().unwrap().into()),
|
true => Ok(Some(rlp.data()?.into())),
|
||||||
false => None,
|
false => Ok(None),
|
||||||
};
|
}?;
|
||||||
let rest = rlp::Rlp::new(
|
let rest = rlp::Rlp::new(
|
||||||
rlp.as_raw().get(1..).ok_or(rlp::DecoderError::Custom("no transaction payload"))?,
|
rlp.as_raw().get(1..).ok_or(rlp::DecoderError::Custom("no transaction payload"))?,
|
||||||
);
|
);
|
||||||
|
@ -439,6 +498,37 @@ mod tests {
|
||||||
assert_eq!(expected, actual);
|
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"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_eip155_decode() {
|
fn test_eip155_decode() {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use super::{decode_to, normalize_v, request::TransactionRequest};
|
use super::{extract_chain_id, normalize_v};
|
||||||
use crate::types::{Address, Bytes, Signature, Transaction, H256, U256, U64};
|
use crate::types::{Address, Bytes, Signature, Transaction, TransactionRequest, H256, U256, U64};
|
||||||
|
|
||||||
use rlp::{Decodable, DecoderError, RlpStream};
|
use rlp::{Decodable, DecoderError, RlpStream};
|
||||||
use rlp_derive::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
|
use rlp_derive::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -104,42 +103,37 @@ impl Eip2930TransactionRequest {
|
||||||
rlp.out().freeze().into()
|
rlp.out().freeze().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes fields based on the RLP offset passed
|
/// Decodes fields based on the RLP offset passed.
|
||||||
fn decode_base_rlp(&mut self, rlp: &rlp::Rlp, offset: &mut usize) -> Result<(), DecoderError> {
|
fn decode_base_rlp(rlp: &rlp::Rlp, offset: &mut usize) -> Result<Self, DecoderError> {
|
||||||
self.tx.chain_id = Some(rlp.val_at(*offset)?);
|
let request = TransactionRequest::decode_unsigned_rlp_base(rlp, offset)?;
|
||||||
*offset += 1;
|
let access_list = rlp.val_at(*offset)?;
|
||||||
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)?);
|
|
||||||
*offset += 1;
|
*offset += 1;
|
||||||
|
|
||||||
self.tx.gas = Some(rlp.val_at(*offset)?);
|
Ok(Self { tx: request, access_list })
|
||||||
*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(())
|
/// 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
|
/// Get a Eip2930TransactionRequest from a rlp encoded byte stream
|
||||||
impl Decodable for Eip2930TransactionRequest {
|
impl Decodable for Eip2930TransactionRequest {
|
||||||
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
|
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
|
||||||
let mut new_tx = Self::new(TransactionRequest::new(), AccessList::default());
|
Self::decode_base_rlp(rlp, &mut 0)
|
||||||
let mut offset = 0;
|
|
||||||
new_tx.decode_base_rlp(rlp, &mut offset)?;
|
|
||||||
Ok(new_tx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
//! Transaction types
|
//! Transaction types
|
||||||
use super::{decode_to, extract_chain_id, rlp_opt, NUM_TX_FIELDS};
|
use super::{decode_to, extract_chain_id, rlp_opt, NUM_TX_FIELDS};
|
||||||
use crate::{
|
use crate::{
|
||||||
types::{Address, Bytes, NameOrAddress, Signature, Transaction, H256, U256, U64},
|
types::{
|
||||||
|
Address, Bytes, NameOrAddress, Signature, SignatureError, Transaction, H256, U256, U64,
|
||||||
|
},
|
||||||
utils::keccak256,
|
utils::keccak256,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rlp::{Decodable, RlpStream};
|
use rlp::{Decodable, RlpStream};
|
||||||
use serde::{Deserialize, Serialize};
|
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
|
/// Parameters for sending a transaction
|
||||||
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
|
#[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
|
/// Decodes the unsigned rlp, returning the transaction request and incrementing the counter
|
||||||
/// passed as we are traversing the rlp list.
|
/// passed as we are traversing the rlp list.
|
||||||
fn decode_unsigned_rlp_base(
|
pub(crate) fn decode_unsigned_rlp_base(
|
||||||
rlp: &rlp::Rlp,
|
rlp: &rlp::Rlp,
|
||||||
offset: &mut usize,
|
offset: &mut usize,
|
||||||
) -> Result<Self, rlp::DecoderError> {
|
) -> Result<Self, rlp::DecoderError> {
|
||||||
|
@ -249,12 +263,12 @@ impl TransactionRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes the given RLP into a transaction, attempting to decode its signature as well.
|
/// 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 offset = 0;
|
||||||
let mut txn = Self::decode_unsigned_rlp_base(rlp, &mut offset)?;
|
let mut txn = Self::decode_unsigned_rlp_base(rlp, &mut offset)?;
|
||||||
|
|
||||||
let v = rlp.at(offset)?.as_val()?;
|
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);
|
txn.chain_id = extract_chain_id(v);
|
||||||
offset += 1;
|
offset += 1;
|
||||||
let r = rlp.at(offset)?.as_val()?;
|
let r = rlp.at(offset)?.as_val()?;
|
||||||
|
@ -262,6 +276,8 @@ impl TransactionRequest {
|
||||||
let s = rlp.at(offset)?.as_val()?;
|
let s = rlp.at(offset)?.as_val()?;
|
||||||
|
|
||||||
let sig = Signature { r, s, v };
|
let sig = Signature { r, s, v };
|
||||||
|
txn.from = Some(sig.recover(txn.sighash())?);
|
||||||
|
|
||||||
Ok((txn, sig))
|
Ok((txn, sig))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,7 +285,7 @@ impl TransactionRequest {
|
||||||
impl Decodable for TransactionRequest {
|
impl Decodable for TransactionRequest {
|
||||||
/// Decodes the given RLP into a transaction request, ignoring the signature if populated
|
/// Decodes the given RLP into a transaction request, ignoring the signature if populated
|
||||||
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
|
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(test)]
|
||||||
#[cfg(not(feature = "celo"))]
|
#[cfg(not(feature = "celo"))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::types::Signature;
|
use crate::types::{NameOrAddress, Signature};
|
||||||
use rlp::{Decodable, Rlp};
|
use rlp::{Decodable, Rlp};
|
||||||
|
|
||||||
use super::{Address, TransactionRequest, U256, U64};
|
use super::{Address, TransactionRequest, U256, U64};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn encode_decode_rlp() {
|
fn encode_decode_rlp() {
|
||||||
|
@ -469,4 +486,54 @@ mod tests {
|
||||||
assert_eq!(expected_sig, decoded_sig);
|
assert_eq!(expected_sig, decoded_sig);
|
||||||
assert_eq!(decoded_tx.chain_id, Some(U64::from(1)));
|
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 {
|
impl Decodable for Transaction {
|
||||||
fn decode(rlp: &rlp::Rlp) -> Result<Self, DecoderError> {
|
fn decode(rlp: &rlp::Rlp) -> Result<Self, DecoderError> {
|
||||||
let mut txn = Self::default();
|
let mut txn = Self::default();
|
||||||
// we can get the type from the first value
|
// we can get the type from the first value
|
||||||
let mut offset = 0;
|
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(
|
let rest = rlp::Rlp::new(
|
||||||
rlp.as_raw().get(1..).ok_or(DecoderError::Custom("no transaction payload"))?,
|
rlp.as_raw().get(1..).ok_or(DecoderError::Custom("no transaction payload"))?,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue