|
| 1 | +// Warning: Part of the Consensus protocol, changes need to produce *exactly* the same result or |
| 2 | +// block verification will fail. Intentional breaking changes constitute a fork. |
| 3 | + |
| 4 | +use ethereum_types::{Address, U256}; |
| 5 | +use std::collections::HashMap; |
| 6 | +use types::transaction::SignedTransaction; |
| 7 | + |
| 8 | +/// Combining an address with a random U256 seed using XOR, using big-endian byte ordering always. |
| 9 | +fn address_xor_u256(address: &Address, seed: U256) -> Address { |
| 10 | + // Address bytes are always assuming big-endian order. |
| 11 | + let address_bytes = address.as_bytes(); |
| 12 | + |
| 13 | + // Explicitly convert U256 to big endian order |
| 14 | + let mut seed_bytes = [0u8; 32]; |
| 15 | + seed.to_big_endian(&mut seed_bytes); |
| 16 | + |
| 17 | + // Byte-wise XOR, constructing a new, big-endian array |
| 18 | + let mut result = [0u8; 20]; |
| 19 | + for i in 0..20 { |
| 20 | + result[i] = address_bytes[i] ^ seed_bytes[i]; |
| 21 | + } |
| 22 | + |
| 23 | + // Construct a new Address from the big-endian array |
| 24 | + Address::from(result) |
| 25 | +} |
| 26 | + |
| 27 | +/// The list of transactions is expected to be free of duplicates. |
| 28 | +fn deterministic_transactions_shuffling( |
| 29 | + transactions: Vec<SignedTransaction>, |
| 30 | + seed: U256, |
| 31 | +) -> Vec<SignedTransaction> { |
| 32 | + // The implementation needs to be both portable and deterministic. |
| 33 | + // There is no guarantee that the input list of transactions does not contain transactions |
| 34 | + // with the same nonce but different content. |
| 35 | + // There is also no guarantee the transactions are sorted by nonce. |
| 36 | + |
| 37 | + // Group transactions by sender. |
| 38 | + // * Walk the transactions from first to last |
| 39 | + // * Add transactions with unique nonce to a per-sender vector |
| 40 | + // * Discard transactions with a nonce already existing in the list of transactions |
| 41 | + let mut txs_by_sender: HashMap<_, Vec<SignedTransaction>> = HashMap::new(); |
| 42 | + for tx in transactions { |
| 43 | + let sender = tx.sender(); |
| 44 | + let entry = txs_by_sender.entry(sender).or_insert_with(Vec::new); |
| 45 | + |
| 46 | + if let Some(existing_tx) = entry |
| 47 | + .iter_mut() |
| 48 | + .find(|existing_tx| existing_tx.tx().nonce == tx.tx().nonce) |
| 49 | + { |
| 50 | + if tx.tx().gas_price > existing_tx.tx().gas_price { |
| 51 | + *existing_tx = tx; |
| 52 | + } |
| 53 | + } else { |
| 54 | + entry.push(tx); |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + // For each sender, sort their transactions by nonce (lowest first). |
| 59 | + // Nonces are expected to be unique at this point, guaranteeing portable |
| 60 | + // and deterministic results independent of the sorting algorithm as long as |
| 61 | + // the sorting algorithm works and is implemented correctly. |
| 62 | + for txs in txs_by_sender.values_mut() { |
| 63 | + txs.sort_by_key(|tx| tx.tx().nonce); |
| 64 | + } |
| 65 | + |
| 66 | + // Deterministically randomize the order of senders. |
| 67 | + // Same as with transactions we rely on the uniqueness of list members and |
| 68 | + // a properly functioning sorting algorithm. To prevent predictable order we |
| 69 | + // XOR each sender address with the random number generated through the HBBFT |
| 70 | + // protocol, and use the resulting address as sorting key. |
| 71 | + // The random number is guaranteed to be identical for all validators at the |
| 72 | + // time of block creation. |
| 73 | + let mut senders: Vec<_> = txs_by_sender.keys().cloned().collect(); |
| 74 | + senders.sort_by_key(|address| address_xor_u256(address, seed)); |
| 75 | + |
| 76 | + // Create the final transaction list by iterating over the randomly shuffled senders. |
| 77 | + let mut final_transactions = Vec::new(); |
| 78 | + for sender in senders { |
| 79 | + if let Some(mut sender_txs) = txs_by_sender.remove(&sender) { |
| 80 | + // Each sender's transactions are already sorted by nonce. |
| 81 | + final_transactions.append(&mut sender_txs); |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + final_transactions |
| 86 | +} |
| 87 | + |
| 88 | +#[cfg(test)] |
| 89 | +mod tests { |
| 90 | + use super::*; |
| 91 | + // Convert to bytes in big-endian order. |
| 92 | + |
| 93 | + fn u64_to_bytes_be<const N: usize>(n: u64) -> [u8; N] { |
| 94 | + // Make sure the array is large enough to hold 8 bytes. |
| 95 | + assert!(N >= 8, "Target array size must be at least 8 bytes"); |
| 96 | + let mut result = [0u8; N]; |
| 97 | + // Copy the big-endian bytes into the first 8 bytes. |
| 98 | + result[..8].copy_from_slice(&n.to_be_bytes()); |
| 99 | + result |
| 100 | + } |
| 101 | + |
| 102 | + #[test] |
| 103 | + fn test_address_xor_u256() { |
| 104 | + // TODO: Cover corner cases, preferably by using a testing crate like proptest. |
| 105 | + let address_value = 0x1234567890abcdefu64; |
| 106 | + let seed_value = 0x7a9e4b3d1c2f0a68u64; |
| 107 | + |
| 108 | + let address_bytes: [u8; 20] = u64_to_bytes_be(address_value); |
| 109 | + let address = Address::from_slice(&address_bytes); |
| 110 | + let seed_bytes: [u8; 32] = u64_to_bytes_be(seed_value); |
| 111 | + let seed = U256::from_big_endian(&seed_bytes); |
| 112 | + let result = address_xor_u256(&address, seed); |
| 113 | + assert_eq!( |
| 114 | + result, |
| 115 | + Address::from_slice(&u64_to_bytes_be::<20>(address_value ^ seed_value)) |
| 116 | + ); |
| 117 | + } |
| 118 | +} |
0 commit comments