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

EIP-1559 Support #533

Merged
merged 10 commits into from
Aug 19, 2021
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