Skip to content

Commit

Permalink
EIP-1559 Support (#533)
Browse files Browse the repository at this point in the history
* Added EIP-1559 fields

* Fixed duplicate base_fee_per_gas

* Fixed tests

* Transaction type 1 and 2 signing

* Typed tx should be signed directly without manipulating v-value

* Removed unused code, cargo fmt

* transaction.gas_price optional

* * merge master

* const tx type ids

* use signing CONTEXT

Co-authored-by: mdben1247 <mdben1247@users.noreply.github.com>
Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 19, 2021
1 parent 4a8ec20 commit 2e2d1df
Show file tree
Hide file tree
Showing 13 changed files with 272 additions and 50 deletions.
205 changes: 166 additions & 39 deletions src/api/accounts.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
//! Partial implementation of the `Accounts` namespace.

use crate::{api::Namespace, signing, types::H256, Transport};
use crate::{
api::Namespace,
signing,
types::{AccessList, H256, U64},
Transport,
};

/// `Accounts` namespace
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -54,6 +59,10 @@ mod accounts_signing {
use rlp::RlpStream;
use std::convert::TryInto;

const LEGACY_TX_ID: u64 = 0;
const ACCESSLISTS_TX_ID: u64 = 1;
const EIP1559_TX_ID: u64 = 2;

impl<T: Transport> Accounts<T> {
/// Gets the parent `web3` namespace
fn web3(&self) -> Web3<T> {
Expand Down Expand Up @@ -82,21 +91,41 @@ mod accounts_signing {
};
}
let from = key.address();

let gas_price = match tx.transaction_type {
Some(tx_type) if tx_type == U64::from(EIP1559_TX_ID) && tx.max_fee_per_gas.is_some() => {
tx.max_fee_per_gas
}
_ => tx.gas_price,
};

let (nonce, gas_price, chain_id) = futures::future::try_join3(
maybe!(tx.nonce, self.web3().eth().transaction_count(from, None)),
maybe!(tx.gas_price, self.web3().eth().gas_price()),
maybe!(gas_price, self.web3().eth().gas_price()),
maybe!(tx.chain_id.map(U256::from), self.web3().eth().chain_id()),
)
.await?;
let chain_id = chain_id.as_u64();

let max_priority_fee_per_gas = match tx.transaction_type {
Some(tx_type) if tx_type == U64::from(EIP1559_TX_ID) => {
tx.max_priority_fee_per_gas.unwrap_or(gas_price)
}
_ => gas_price,
};

let tx = Transaction {
to: tx.to,
nonce,
gas: tx.gas,
gas_price,
value: tx.value,
data: tx.data.0,
transaction_type: tx.transaction_type,
access_list: tx.access_list.unwrap_or_default(),
max_priority_fee_per_gas,
};

let signed = tx.sign(key, chain_id);
Ok(signed)
}
Expand Down Expand Up @@ -165,74 +194,169 @@ mod accounts_signing {
}
}
/// A transaction used for RLP encoding, hashing and signing.
#[derive(Debug)]
pub struct Transaction {
pub to: Option<Address>,
pub nonce: U256,
pub gas: U256,
pub gas_price: U256,
pub value: U256,
pub data: Vec<u8>,
pub transaction_type: Option<U64>,
pub access_list: AccessList,
pub max_priority_fee_per_gas: U256,
}

impl Transaction {
/// RLP encode an unsigned transaction for the specified chain ID.
fn rlp_append_unsigned(&self, rlp: &mut RlpStream, chain_id: u64) {
rlp.begin_list(9);
rlp.append(&self.nonce);
rlp.append(&self.gas_price);
rlp.append(&self.gas);
fn rlp_append_legacy(&self, stream: &mut RlpStream) {
stream.append(&self.nonce);
stream.append(&self.gas_price);
stream.append(&self.gas);
if let Some(to) = self.to {
rlp.append(&to);
stream.append(&to);
} else {
stream.append(&"");
}
stream.append(&self.value);
stream.append(&self.data);
}

fn encode_legacy(&self, chain_id: u64, signature: Option<&Signature>) -> RlpStream {
let mut stream = RlpStream::new();
stream.begin_list(9);

self.rlp_append_legacy(&mut stream);

if let Some(signature) = signature {
self.rlp_append_signature(&mut stream, signature);
} else {
rlp.append(&"");
stream.append(&chain_id);
stream.append(&0u8);
stream.append(&0u8);
}
rlp.append(&self.value);
rlp.append(&self.data);
rlp.append(&chain_id);
rlp.append(&0u8);
rlp.append(&0u8);

stream
}

/// RLP encode a signed transaction with the specified signature.
fn rlp_append_signed(&self, rlp: &mut RlpStream, signature: &Signature) {
rlp.begin_list(9);
rlp.append(&self.nonce);
rlp.append(&self.gas_price);
rlp.append(&self.gas);
fn encode_access_list_payload(&self, chain_id: u64, signature: Option<&Signature>) -> RlpStream {
let mut stream = RlpStream::new();

let list_size = if signature.is_some() { 11 } else { 8 };
stream.begin_list(list_size);

// append chain_id. from EIP-2930: chainId is defined to be an integer of arbitrary size.
stream.append(&chain_id);

self.rlp_append_legacy(&mut stream);
self.rlp_append_access_list(&mut stream);

if let Some(signature) = signature {
self.rlp_append_signature(&mut stream, signature);
}

stream
}

fn encode_eip1559_payload(&self, chain_id: u64, signature: Option<&Signature>) -> RlpStream {
let mut stream = RlpStream::new();

let list_size = if signature.is_some() { 12 } else { 9 };
stream.begin_list(list_size);

// append chain_id. from EIP-2930: chainId is defined to be an integer of arbitrary size.
stream.append(&chain_id);

stream.append(&self.nonce);
stream.append(&self.max_priority_fee_per_gas);
stream.append(&self.gas_price);
stream.append(&self.gas);
if let Some(to) = self.to {
rlp.append(&to);
stream.append(&to);
} else {
rlp.append(&"");
stream.append(&"");
}
stream.append(&self.value);
stream.append(&self.data);

self.rlp_append_access_list(&mut stream);

if let Some(signature) = signature {
self.rlp_append_signature(&mut stream, signature);
}

stream
}

fn rlp_append_signature(&self, stream: &mut RlpStream, signature: &Signature) -> () {
stream.append(&signature.v);
stream.append(&U256::from_big_endian(signature.r.as_bytes()));
stream.append(&U256::from_big_endian(signature.s.as_bytes()));
}

fn rlp_append_access_list(&self, stream: &mut RlpStream) -> () {
stream.begin_list(self.access_list.len());
for access in self.access_list.iter() {
stream.begin_list(2);
stream.append(&access.address);
stream.begin_list(access.storage_keys.len());
for storage_key in access.storage_keys.iter() {
stream.append(storage_key);
}
}
}

fn encode(&self, chain_id: u64, signature: Option<&Signature>) -> Vec<u8> {
match self.transaction_type.map(|t| t.as_u64()) {
Some(LEGACY_TX_ID) | None => {
let stream = self.encode_legacy(chain_id, signature);
stream.out().to_vec()
}

Some(ACCESSLISTS_TX_ID) => {
let tx_id: u8 = ACCESSLISTS_TX_ID as u8;
let stream = self.encode_access_list_payload(chain_id, signature);
[&[tx_id], stream.as_raw()].concat()
}

Some(EIP1559_TX_ID) => {
let tx_id: u8 = EIP1559_TX_ID as u8;
let stream = self.encode_eip1559_payload(chain_id, signature);
[&[tx_id], stream.as_raw()].concat()
}

_ => {
panic!("Unsupported transaction type");
}
}
rlp.append(&self.value);
rlp.append(&self.data);
rlp.append(&signature.v);
rlp.append(&U256::from_big_endian(signature.r.as_bytes()));
rlp.append(&U256::from_big_endian(signature.s.as_bytes()));
}

/// Sign and return a raw signed transaction.
pub fn sign(self, sign: impl signing::Key, chain_id: u64) -> SignedTransaction {
let mut rlp = RlpStream::new();
self.rlp_append_unsigned(&mut rlp, chain_id);
let adjust_v_value = match self.transaction_type.map(|t| t.as_u64()) {
Some(LEGACY_TX_ID) | None => true,
_ => false,
};

let hash = signing::keccak256(rlp.as_raw());
let signature = sign
.sign(&hash, Some(chain_id))
.expect("hash is non-zero 32-bytes; qed");
let encoded = self.encode(chain_id, None);

let hash = signing::keccak256(encoded.as_ref());

rlp.clear();
self.rlp_append_signed(&mut rlp, &signature);
let signature = if adjust_v_value {
sign.sign(&hash, Some(chain_id))
.expect("hash is non-zero 32-bytes; qed")
} else {
sign.sign_message(&hash).expect("hash is non-zero 32-bytes; qed")
};

let transaction_hash = signing::keccak256(rlp.as_raw()).into();
let raw_transaction = rlp.out().to_vec().into();
let signed = self.encode(chain_id, Some(&signature));
let transaction_hash = signing::keccak256(signed.as_ref()).into();

SignedTransaction {
message_hash: hash.into(),
v: signature.v,
r: signature.r,
s: signature.s,
raw_transaction,
raw_transaction: signed.into(),
transaction_hash,
}
}
Expand Down Expand Up @@ -431,6 +555,9 @@ mod tests {
to: Some(hex!("F0109fC8DF283027b6285cc889F5aA624EaC1F55").into()),
value: 1_000_000_000.into(),
data: Vec::new(),
transaction_type: None,
access_list: vec![],
max_priority_fee_per_gas: 0.into(),
};
let skey = SecretKey::from_slice(&hex!(
"4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
Expand Down
8 changes: 7 additions & 1 deletion src/api/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,8 @@ mod tests {
"contractAddress": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"logsBloom": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
"logs": [],
"status": "0x1"
"status": "0x1",
"effectiveGasPrice": "0x100"
}"#;

rpc_test! (
Expand All @@ -488,6 +489,7 @@ mod tests {
gas: None, gas_price: None,
value: Some(0x1.into()), data: None,
transaction_type: None, access_list: None,
max_fee_per_gas: None, max_priority_fee_per_gas: None,
}, None
=>
"eth_call", vec![r#"{"to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#, r#""latest""#];
Expand Down Expand Up @@ -520,6 +522,7 @@ mod tests {
gas: None, gas_price: None,
value: Some(0x1.into()), data: None,
transaction_type: None, access_list: None,
max_fee_per_gas: None, max_priority_fee_per_gas: None,
}, None
=>
"eth_estimateGas", vec![r#"{"to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#];
Expand All @@ -532,6 +535,7 @@ mod tests {
gas: None, gas_price: None,
value: Some(0x1.into()), data: None,
transaction_type: None, access_list: None,
max_fee_per_gas: None, max_priority_fee_per_gas: None,
}, None
=>
"eth_estimateGas", vec![r#"{"value":"0x1"}"#];
Expand All @@ -544,6 +548,7 @@ mod tests {
gas: None, gas_price: None,
value: Some(0x1.into()), data: None,
transaction_type: None, access_list: None,
max_fee_per_gas: None, max_priority_fee_per_gas: None,
}, Some(0x123.into())
=>
"eth_estimateGas", vec![r#"{"to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#, r#""0x123""#];
Expand Down Expand Up @@ -782,6 +787,7 @@ mod tests {
value: Some(0x1.into()), data: None,
nonce: None, condition: None,
transaction_type: None, access_list: None,
max_fee_per_gas: None, max_priority_fee_per_gas: None,
}
=>
"eth_sendTransaction", vec![r#"{"from":"0x0000000000000000000000000000000000000123","gasPrice":"0x1","to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#];
Expand Down
6 changes: 6 additions & 0 deletions src/api/parity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ mod tests {
data: None,
transaction_type: None,
access_list: None,
max_fee_per_gas: None,
max_priority_fee_per_gas: None,
},
CallRequest {
from: Some(Address::from_low_u64_be(0x321)),
Expand All @@ -109,6 +111,8 @@ mod tests {
data: Some(hex!("0493").into()),
transaction_type: None,
access_list: None,
max_fee_per_gas: None,
max_priority_fee_per_gas: None,
},
CallRequest {
from: None,
Expand All @@ -119,6 +123,8 @@ mod tests {
data: Some(hex!("0723").into()),
transaction_type: None,
access_list: None,
max_fee_per_gas: None,
max_priority_fee_per_gas: None,
}
] => "parity_call", vec![
r#"[{"to":"0x0000000000000000000000000000000000000123","value":"0x1"},{"data":"0x0493","from":"0x0000000000000000000000000000000000000321","to":"0x0000000000000000000000000000000000000123"},{"data":"0x0723","to":"0x0000000000000000000000000000000000000765","value":"0x5"}]"#
Expand Down
3 changes: 3 additions & 0 deletions src/api/personal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ mod tests {
value: Some(0x1.into()), data: None,
nonce: None, condition: None,
transaction_type: None, access_list: None,
max_fee_per_gas: None, max_priority_fee_per_gas: None,
}, "hunter2"
=>
"personal_sendTransaction", vec![r#"{"from":"0x0000000000000000000000000000000000000123","gasPrice":"0x1","to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#, r#""hunter2""#];
Expand All @@ -161,6 +162,8 @@ mod tests {
condition: None,
transaction_type: None,
access_list: None,
max_fee_per_gas: None,
max_priority_fee_per_gas: None,
}, "hunter2"
=>
"personal_signTransaction", vec![r#"{"data":"0x603880600c6000396000f300603880600c6000396000f3603880600c6000396000f360","from":"0x407d73d8a49eeb85d32cf465507dd71d507100c1","gas":"0x7f110","gasPrice":"0x9184e72a000","nonce":"0x0","to":"0x853f43d8a49eeb85d32cf465507dd71d507100c1","value":"0x7f110"}"#, r#""hunter2""#];
Expand Down
1 change: 1 addition & 0 deletions src/api/traces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ mod tests {
gas: None, gas_price: None,
value: Some(0x1.into()), data: None,
transaction_type: None, access_list: None,
max_fee_per_gas: None, max_priority_fee_per_gas: None,
}, vec![TraceType::Trace], None
=>
"trace_call", vec![r#"{"to":"0x0000000000000000000000000000000000000123","value":"0x1"}"#, r#"["trace"]"#, r#""latest""#];
Expand Down
Loading

0 comments on commit 2e2d1df

Please sign in to comment.