Skip to content

Commit ec4c978

Browse files
committed
[Merge] Add serde impls for Transactions type (#2649)
* Start implemented serde for transactions * Revise serde impl * Add tests for transaction decoding
1 parent 558cdc9 commit ec4c978

File tree

1 file changed

+214
-5
lines changed
  • beacon_node/execution_layer/src/engine_api

1 file changed

+214
-5
lines changed

beacon_node/execution_layer/src/engine_api/http.rs

Lines changed: 214 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ pub struct JsonPayloadId {
267267
pub payload_id: u64,
268268
}
269269

270-
#[derive(Debug, PartialEq, Serialize, Deserialize)]
270+
#[derive(Debug, PartialEq, Default, Serialize, Deserialize)]
271271
#[serde(bound = "T: EthSpec", rename_all = "camelCase")]
272272
pub struct JsonExecutionPayload<T: EthSpec> {
273273
pub parent_hash: Hash256,
@@ -285,13 +285,11 @@ pub struct JsonExecutionPayload<T: EthSpec> {
285285
pub gas_used: u64,
286286
#[serde(with = "eth2_serde_utils::u64_hex_be")]
287287
pub timestamp: u64,
288-
// FIXME(paul): check serialization
289288
#[serde(with = "ssz_types::serde_utils::hex_var_list")]
290289
pub extra_data: VariableList<u8, T::MaxExtraDataBytes>,
291290
pub base_fee_per_gas: Uint256,
292291
pub block_hash: Hash256,
293-
// FIXME(paul): add transaction parsing.
294-
#[serde(default, skip_deserializing)]
292+
#[serde(with = "serde_transactions")]
295293
pub transactions: VariableList<Transaction<T>, T::MaxTransactionsPerPayload>,
296294
}
297295

@@ -357,7 +355,7 @@ pub struct JsonForkChoiceUpdatedRequest {
357355
pub finalized_block_hash: Hash256,
358356
}
359357

360-
// Serializes the `logs_bloom` field.
358+
/// Serializes the `logs_bloom` field of an `ExecutionPayload`.
361359
pub mod serde_logs_bloom {
362360
use super::*;
363361
use eth2_serde_utils::hex::PrefixedHexVisitor;
@@ -386,6 +384,81 @@ pub mod serde_logs_bloom {
386384
}
387385
}
388386

387+
/// Serializes the `transactions` field of an `ExecutionPayload`.
388+
pub mod serde_transactions {
389+
use super::*;
390+
use eth2_serde_utils::hex;
391+
use serde::ser::SerializeSeq;
392+
use serde::{de, Deserializer, Serializer};
393+
use std::marker::PhantomData;
394+
395+
type Value<T, N> = VariableList<Transaction<T>, N>;
396+
397+
#[derive(Default)]
398+
pub struct ListOfBytesListVisitor<T, N> {
399+
_phantom_t: PhantomData<T>,
400+
_phantom_n: PhantomData<N>,
401+
}
402+
403+
impl<'a, T: EthSpec, N: Unsigned> serde::de::Visitor<'a> for ListOfBytesListVisitor<T, N> {
404+
type Value = Value<T, N>;
405+
406+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
407+
write!(formatter, "a list of 0x-prefixed byte lists")
408+
}
409+
410+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
411+
where
412+
A: serde::de::SeqAccess<'a>,
413+
{
414+
let mut outer = VariableList::default();
415+
416+
while let Some(val) = seq.next_element::<String>()? {
417+
let inner_vec = hex::decode(&val).map_err(de::Error::custom)?;
418+
let opaque_transaction = VariableList::new(inner_vec).map_err(|e| {
419+
serde::de::Error::custom(format!("transaction too large: {:?}", e))
420+
})?;
421+
let transaction = Transaction::OpaqueTransaction(opaque_transaction);
422+
outer.push(transaction).map_err(|e| {
423+
serde::de::Error::custom(format!("too many transactions: {:?}", e))
424+
})?;
425+
}
426+
427+
Ok(outer)
428+
}
429+
}
430+
431+
pub fn serialize<S, T: EthSpec, N: Unsigned>(
432+
value: &Value<T, N>,
433+
serializer: S,
434+
) -> Result<S::Ok, S::Error>
435+
where
436+
S: Serializer,
437+
{
438+
let mut seq = serializer.serialize_seq(Some(value.len()))?;
439+
for transaction in value {
440+
// It's important to match on the inner values of the transaction. Serializing the
441+
// entire `Transaction` will result in appending the SSZ union prefix byte. The
442+
// execution node does not want that.
443+
let hex = match transaction {
444+
Transaction::OpaqueTransaction(val) => hex::encode(&val[..]),
445+
};
446+
seq.serialize_element(&hex)?;
447+
}
448+
seq.end()
449+
}
450+
451+
pub fn deserialize<'de, D, T: EthSpec, N: Unsigned>(
452+
deserializer: D,
453+
) -> Result<Value<T, N>, D::Error>
454+
where
455+
D: Deserializer<'de>,
456+
{
457+
let visitor: ListOfBytesListVisitor<T, N> = <_>::default();
458+
deserializer.deserialize_any(visitor)
459+
}
460+
}
461+
389462
#[cfg(test)]
390463
mod test {
391464
use super::*;
@@ -443,6 +516,142 @@ mod test {
443516

444517
const LOGS_BLOOM_01: &str = "0x01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101";
445518

519+
fn encode_transactions<E: EthSpec>(
520+
transactions: VariableList<Transaction<E>, E::MaxTransactionsPerPayload>,
521+
) -> Result<serde_json::Value, serde_json::Error> {
522+
let ep: JsonExecutionPayload<E> = JsonExecutionPayload {
523+
transactions,
524+
..<_>::default()
525+
};
526+
let json = serde_json::to_value(&ep)?;
527+
Ok(json.get("transactions").unwrap().clone())
528+
}
529+
530+
fn decode_transactions<E: EthSpec>(
531+
transactions: serde_json::Value,
532+
) -> Result<VariableList<Transaction<E>, E::MaxTransactionsPerPayload>, serde_json::Error> {
533+
let json = json!({
534+
"parentHash": HASH_00,
535+
"coinbase": ADDRESS_01,
536+
"stateRoot": HASH_01,
537+
"receiptRoot": HASH_00,
538+
"logsBloom": LOGS_BLOOM_01,
539+
"random": HASH_01,
540+
"blockNumber": "0x0",
541+
"gasLimit": "0x1",
542+
"gasUsed": "0x2",
543+
"timestamp": "0x2a",
544+
"extraData": "0x",
545+
"baseFeePerGas": "0x1",
546+
"blockHash": HASH_01,
547+
"transactions": transactions,
548+
});
549+
let ep: JsonExecutionPayload<E> = serde_json::from_value(json)?;
550+
Ok(ep.transactions)
551+
}
552+
553+
fn assert_transactions_serde<E: EthSpec>(
554+
name: &str,
555+
as_obj: VariableList<Transaction<E>, E::MaxTransactionsPerPayload>,
556+
as_json: serde_json::Value,
557+
) {
558+
assert_eq!(
559+
encode_transactions(as_obj.clone()).unwrap(),
560+
as_json,
561+
"encoding for {}",
562+
name
563+
);
564+
assert_eq!(
565+
decode_transactions(as_json).unwrap(),
566+
as_obj,
567+
"decoding for {}",
568+
name
569+
);
570+
}
571+
572+
/// Example: if `spec == &[1, 1]`, then two one-byte transactions will be created.
573+
fn generate_opaque_transactions<E: EthSpec>(
574+
spec: &[usize],
575+
) -> VariableList<Transaction<E>, E::MaxTransactionsPerPayload> {
576+
let mut txs = VariableList::default();
577+
578+
for &num_bytes in spec {
579+
let mut tx = VariableList::default();
580+
for _ in 0..num_bytes {
581+
tx.push(0).unwrap();
582+
}
583+
txs.push(Transaction::OpaqueTransaction(tx)).unwrap();
584+
}
585+
586+
txs
587+
}
588+
589+
#[test]
590+
fn transaction_serde() {
591+
assert_transactions_serde::<MainnetEthSpec>(
592+
"empty",
593+
generate_opaque_transactions(&[]),
594+
json!([]),
595+
);
596+
assert_transactions_serde::<MainnetEthSpec>(
597+
"one empty tx",
598+
generate_opaque_transactions(&[0]),
599+
json!(["0x"]),
600+
);
601+
assert_transactions_serde::<MainnetEthSpec>(
602+
"two empty txs",
603+
generate_opaque_transactions(&[0, 0]),
604+
json!(["0x", "0x"]),
605+
);
606+
assert_transactions_serde::<MainnetEthSpec>(
607+
"one one-byte tx",
608+
generate_opaque_transactions(&[1]),
609+
json!(["0x00"]),
610+
);
611+
assert_transactions_serde::<MainnetEthSpec>(
612+
"two one-byte txs",
613+
generate_opaque_transactions(&[1, 1]),
614+
json!(["0x00", "0x00"]),
615+
);
616+
assert_transactions_serde::<MainnetEthSpec>(
617+
"mixed bag",
618+
generate_opaque_transactions(&[0, 1, 3, 0]),
619+
json!(["0x", "0x00", "0x000000", "0x"]),
620+
);
621+
622+
/*
623+
* Check for too many transactions
624+
*/
625+
626+
let num_max_txs = <MainnetEthSpec as EthSpec>::MaxTransactionsPerPayload::to_usize();
627+
let max_txs = (0..num_max_txs).map(|_| "0x00").collect::<Vec<_>>();
628+
let too_many_txs = (0..=num_max_txs).map(|_| "0x00").collect::<Vec<_>>();
629+
630+
decode_transactions::<MainnetEthSpec>(serde_json::to_value(max_txs).unwrap()).unwrap();
631+
assert!(
632+
decode_transactions::<MainnetEthSpec>(serde_json::to_value(too_many_txs).unwrap())
633+
.is_err()
634+
);
635+
636+
/*
637+
* Check for transaction too large
638+
*/
639+
640+
use eth2_serde_utils::hex;
641+
642+
let num_max_bytes = <MainnetEthSpec as EthSpec>::MaxBytesPerOpaqueTransaction::to_usize();
643+
let max_bytes = (0..num_max_bytes).map(|_| 0_u8).collect::<Vec<_>>();
644+
let too_many_bytes = (0..=num_max_bytes).map(|_| 0_u8).collect::<Vec<_>>();
645+
decode_transactions::<MainnetEthSpec>(
646+
serde_json::to_value(&[hex::encode(&max_bytes)]).unwrap(),
647+
)
648+
.unwrap();
649+
assert!(decode_transactions::<MainnetEthSpec>(
650+
serde_json::to_value(&[hex::encode(&too_many_bytes)]).unwrap()
651+
)
652+
.is_err());
653+
}
654+
446655
#[tokio::test]
447656
async fn get_block_by_number_request() {
448657
Tester::new()

0 commit comments

Comments
 (0)