From 624e732d5f7516dd7ee211a620e2ec418d85c3b3 Mon Sep 17 00:00:00 2001 From: Dawid Cichuta Date: Tue, 23 Apr 2024 14:03:37 +0200 Subject: [PATCH 1/3] Handle optional binaries with transaction request --- db/migrations/004_transactions_binaries.sql | 1 + src/db.rs | 18 +++++--- src/db/data.rs | 3 ++ src/serde_utils.rs | 1 + src/serde_utils/base64_binary.rs | 47 +++++++++++++++++++++ src/server/routes/transaction.rs | 3 ++ tests/send_too_many_txs.rs | 3 ++ 7 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 db/migrations/004_transactions_binaries.sql create mode 100644 src/serde_utils/base64_binary.rs diff --git a/db/migrations/004_transactions_binaries.sql b/db/migrations/004_transactions_binaries.sql new file mode 100644 index 0000000..45d6337 --- /dev/null +++ b/db/migrations/004_transactions_binaries.sql @@ -0,0 +1 @@ +ALTER TABLE transactions ADD COLUMN binaries BYTEA[] diff --git a/src/db.rs b/src/db.rs index ec2b221..cb14b88 100644 --- a/src/db.rs +++ b/src/db.rs @@ -263,6 +263,7 @@ impl Database { value: U256, gas_limit: U256, priority: TransactionPriority, + binaries: Option>>, relayer_id: &str, ) -> eyre::Result<()> { let mut tx = self.pool.begin().await?; @@ -288,8 +289,8 @@ impl Database { sqlx::query( r#" - INSERT INTO transactions (id, tx_to, data, value, gas_limit, priority, relayer_id, nonce) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + INSERT INTO transactions (id, tx_to, data, value, gas_limit, priority, relayer_id, nonce, binaries) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) "#, ) .bind(tx_id) @@ -300,6 +301,7 @@ impl Database { .bind(priority) .bind(relayer_id) .bind(nonce) + .bind(binaries.map(|b| b)) .execute(tx.as_mut()) .await?; @@ -312,7 +314,7 @@ impl Database { pub async fn get_unsent_txs(&self) -> eyre::Result> { Ok(sqlx::query_as( r#" - SELECT r.id as relayer_id, t.id, t.tx_to, t.data, t.value, t.gas_limit, t.priority, t.nonce, r.key_id, r.chain_id + SELECT r.id as relayer_id, t.id, t.tx_to, t.data, t.value, t.gas_limit, t.priority, t.nonce, t.binaries, r.key_id, r.chain_id FROM transactions t LEFT JOIN sent_transactions s ON (t.id = s.tx_id) INNER JOIN relayers r ON (t.relayer_id = r.id) @@ -763,7 +765,7 @@ impl Database { Ok(sqlx::query_as( r#" SELECT r.id as relayer_id, t.id, t.tx_to, t.data, t.value, t.gas_limit, t.nonce, - r.key_id, r.chain_id, + t.binaries, r.key_id, r.chain_id, s.initial_max_fee_per_gas, s.initial_max_priority_fee_per_gas, s.escalation_count FROM transactions t JOIN sent_transactions s ON t.id = s.tx_id @@ -847,7 +849,7 @@ impl Database { Ok(sqlx::query_as( r#" SELECT t.id as tx_id, t.tx_to as to, t.data, t.value, t.gas_limit, t.nonce, - h.tx_hash, s.status + t.binaries, h.tx_hash, s.status FROM transactions t LEFT JOIN sent_transactions s ON t.id = s.tx_id LEFT JOIN tx_hashes h ON s.valid_tx_hash = h.tx_hash @@ -873,7 +875,7 @@ impl Database { Ok(sqlx::query_as( r#" SELECT t.id as tx_id, t.tx_to as to, t.data, t.value, t.gas_limit, t.nonce, - h.tx_hash, s.status + t. binaries, h.tx_hash, s.status FROM transactions t LEFT JOIN sent_transactions s ON t.id = s.tx_id LEFT JOIN tx_hashes h ON s.valid_tx_hash = h.tx_hash @@ -1405,12 +1407,13 @@ mod tests { let value = U256::from(0); let gas_limit = U256::from(0); let priority = TransactionPriority::Regular; + let binaries = None; let tx = db.read_tx(tx_id).await?; assert!(tx.is_none(), "Tx has not been sent yet"); db.create_transaction( - tx_id, to, data, value, gas_limit, priority, relayer_id, + tx_id, to, data, value, gas_limit, priority, binaries, relayer_id, ) .await?; @@ -1423,6 +1426,7 @@ mod tests { assert_eq!(tx.gas_limit.0, gas_limit); assert_eq!(tx.nonce, 0); assert_eq!(tx.tx_hash, None); + assert_eq!(tx.binaries, None); let unsent_txs = db.read_txs(relayer_id, None).await?; assert_eq!(unsent_txs.len(), 1, "1 unsent tx"); diff --git a/src/db/data.rs b/src/db/data.rs index b9113be..50f1e94 100644 --- a/src/db/data.rs +++ b/src/db/data.rs @@ -19,6 +19,7 @@ pub struct UnsentTx { pub priority: TransactionPriority, #[sqlx(try_from = "i64")] pub nonce: u64, + pub binaries: Option>>, pub key_id: String, #[sqlx(try_from = "i64")] pub chain_id: u64, @@ -34,6 +35,7 @@ pub struct TxForEscalation { pub gas_limit: U256Wrapper, #[sqlx(try_from = "i64")] pub nonce: u64, + pub binaries: Option>>, pub key_id: String, #[sqlx(try_from = "i64")] pub chain_id: u64, @@ -52,6 +54,7 @@ pub struct ReadTxData { pub gas_limit: U256Wrapper, #[sqlx(try_from = "i64")] pub nonce: u64, + pub binaries: Option>>, // Sent tx data pub tx_hash: Option, diff --git a/src/serde_utils.rs b/src/serde_utils.rs index 7f1e57a..596953b 100644 --- a/src/serde_utils.rs +++ b/src/serde_utils.rs @@ -1 +1,2 @@ +pub mod base64_binary; pub mod decimal_u256; diff --git a/src/serde_utils/base64_binary.rs b/src/serde_utils/base64_binary.rs new file mode 100644 index 0000000..e4098b9 --- /dev/null +++ b/src/serde_utils/base64_binary.rs @@ -0,0 +1,47 @@ +use base64::engine::general_purpose; +use base64::Engine as _; +use serde::Deserialize; + +pub fn serialize( + binaries: &Option>>, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + match binaries { + Some(binaries) => { + let base64_vec: Vec = binaries + .iter() + .map(|binary| general_purpose::STANDARD.encode(binary)) + .collect(); + + serializer.serialize_some(&base64_vec) + } + None => serializer.serialize_none(), + } +} + +pub fn deserialize<'de, D>( + deserializer: D, +) -> Result>>, D::Error> +where + D: serde::Deserializer<'de>, +{ + let base64_strings: Option> = + Option::deserialize(deserializer)?; + + match base64_strings { + Some(base64_vec) => { + let decoded_vec: Vec> = base64_vec + .into_iter() + .map(|base64_str| { + general_purpose::STANDARD.decode(base64_str).unwrap() + }) + .collect(); + + Ok(Some(decoded_vec)) + } + None => Ok(None), + } +} diff --git a/src/server/routes/transaction.rs b/src/server/routes/transaction.rs index 45e16d5..020737a 100644 --- a/src/server/routes/transaction.rs +++ b/src/server/routes/transaction.rs @@ -25,6 +25,8 @@ pub struct SendTxRequest { pub priority: TransactionPriority, #[serde(default)] pub tx_id: Option, + #[serde(default, with = "crate::serde_utils::base64_binary")] + pub binaries: Option>>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -116,6 +118,7 @@ pub async fn send_tx( req.value, req.gas_limit, req.priority, + req.binaries, api_token.relayer_id(), ) .await?; diff --git a/tests/send_too_many_txs.rs b/tests/send_too_many_txs.rs index 226cc43..727dc46 100644 --- a/tests/send_too_many_txs.rs +++ b/tests/send_too_many_txs.rs @@ -59,6 +59,7 @@ async fn send_too_many_txs() -> eyre::Result<()> { gas_limit: U256::from(21_000), priority: TransactionPriority::Regular, tx_id: None, + binaries: None, }, ) .await?; @@ -75,6 +76,7 @@ async fn send_too_many_txs() -> eyre::Result<()> { gas_limit: U256::from(21_000), priority: TransactionPriority::Regular, tx_id: None, + binaries: None, }, ) .await; @@ -102,6 +104,7 @@ async fn send_too_many_txs() -> eyre::Result<()> { gas_limit: U256::from(21_000), priority: TransactionPriority::Regular, tx_id: None, + binaries: None, }, ) .await?; From aa4f3cdeee5f19d7a02969e57875254fd9512d34 Mon Sep 17 00:00:00 2001 From: Dawid Cichuta Date: Thu, 25 Apr 2024 13:31:28 +0200 Subject: [PATCH 2/3] Rename binaries to blobs --- db/migrations/004_transactions_binaries.sql | 2 +- src/db.rs | 20 ++++++++++---------- src/db/data.rs | 6 +++--- src/serde_utils/base64_binary.rs | 8 ++++---- src/server/routes/transaction.rs | 4 ++-- tests/send_too_many_txs.rs | 6 +++--- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/db/migrations/004_transactions_binaries.sql b/db/migrations/004_transactions_binaries.sql index 45d6337..3d9bc38 100644 --- a/db/migrations/004_transactions_binaries.sql +++ b/db/migrations/004_transactions_binaries.sql @@ -1 +1 @@ -ALTER TABLE transactions ADD COLUMN binaries BYTEA[] +ALTER TABLE transactions ADD COLUMN blobs BYTEA[] diff --git a/src/db.rs b/src/db.rs index cb14b88..c53cb58 100644 --- a/src/db.rs +++ b/src/db.rs @@ -263,7 +263,7 @@ impl Database { value: U256, gas_limit: U256, priority: TransactionPriority, - binaries: Option>>, + blobs: Option>>, relayer_id: &str, ) -> eyre::Result<()> { let mut tx = self.pool.begin().await?; @@ -289,7 +289,7 @@ impl Database { sqlx::query( r#" - INSERT INTO transactions (id, tx_to, data, value, gas_limit, priority, relayer_id, nonce, binaries) + INSERT INTO transactions (id, tx_to, data, value, gas_limit, priority, relayer_id, nonce, blobs) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) "#, ) @@ -301,7 +301,7 @@ impl Database { .bind(priority) .bind(relayer_id) .bind(nonce) - .bind(binaries.map(|b| b)) + .bind(blobs) .execute(tx.as_mut()) .await?; @@ -314,7 +314,7 @@ impl Database { pub async fn get_unsent_txs(&self) -> eyre::Result> { Ok(sqlx::query_as( r#" - SELECT r.id as relayer_id, t.id, t.tx_to, t.data, t.value, t.gas_limit, t.priority, t.nonce, t.binaries, r.key_id, r.chain_id + SELECT r.id as relayer_id, t.id, t.tx_to, t.data, t.value, t.gas_limit, t.priority, t.nonce, t.blobs, r.key_id, r.chain_id FROM transactions t LEFT JOIN sent_transactions s ON (t.id = s.tx_id) INNER JOIN relayers r ON (t.relayer_id = r.id) @@ -765,7 +765,7 @@ impl Database { Ok(sqlx::query_as( r#" SELECT r.id as relayer_id, t.id, t.tx_to, t.data, t.value, t.gas_limit, t.nonce, - t.binaries, r.key_id, r.chain_id, + t.blobs, r.key_id, r.chain_id, s.initial_max_fee_per_gas, s.initial_max_priority_fee_per_gas, s.escalation_count FROM transactions t JOIN sent_transactions s ON t.id = s.tx_id @@ -849,7 +849,7 @@ impl Database { Ok(sqlx::query_as( r#" SELECT t.id as tx_id, t.tx_to as to, t.data, t.value, t.gas_limit, t.nonce, - t.binaries, h.tx_hash, s.status + t.blobs, h.tx_hash, s.status FROM transactions t LEFT JOIN sent_transactions s ON t.id = s.tx_id LEFT JOIN tx_hashes h ON s.valid_tx_hash = h.tx_hash @@ -875,7 +875,7 @@ impl Database { Ok(sqlx::query_as( r#" SELECT t.id as tx_id, t.tx_to as to, t.data, t.value, t.gas_limit, t.nonce, - t. binaries, h.tx_hash, s.status + t. blobs, h.tx_hash, s.status FROM transactions t LEFT JOIN sent_transactions s ON t.id = s.tx_id LEFT JOIN tx_hashes h ON s.valid_tx_hash = h.tx_hash @@ -1407,13 +1407,13 @@ mod tests { let value = U256::from(0); let gas_limit = U256::from(0); let priority = TransactionPriority::Regular; - let binaries = None; + let blobs = None; let tx = db.read_tx(tx_id).await?; assert!(tx.is_none(), "Tx has not been sent yet"); db.create_transaction( - tx_id, to, data, value, gas_limit, priority, binaries, relayer_id, + tx_id, to, data, value, gas_limit, priority, blobs, relayer_id, ) .await?; @@ -1426,7 +1426,7 @@ mod tests { assert_eq!(tx.gas_limit.0, gas_limit); assert_eq!(tx.nonce, 0); assert_eq!(tx.tx_hash, None); - assert_eq!(tx.binaries, None); + assert_eq!(tx.blobs, None); let unsent_txs = db.read_txs(relayer_id, None).await?; assert_eq!(unsent_txs.len(), 1, "1 unsent tx"); diff --git a/src/db/data.rs b/src/db/data.rs index 50f1e94..8720a6a 100644 --- a/src/db/data.rs +++ b/src/db/data.rs @@ -19,7 +19,7 @@ pub struct UnsentTx { pub priority: TransactionPriority, #[sqlx(try_from = "i64")] pub nonce: u64, - pub binaries: Option>>, + pub blobs: Option>>, pub key_id: String, #[sqlx(try_from = "i64")] pub chain_id: u64, @@ -35,7 +35,7 @@ pub struct TxForEscalation { pub gas_limit: U256Wrapper, #[sqlx(try_from = "i64")] pub nonce: u64, - pub binaries: Option>>, + pub blobs: Option>>, pub key_id: String, #[sqlx(try_from = "i64")] pub chain_id: u64, @@ -54,7 +54,7 @@ pub struct ReadTxData { pub gas_limit: U256Wrapper, #[sqlx(try_from = "i64")] pub nonce: u64, - pub binaries: Option>>, + pub blobs: Option>>, // Sent tx data pub tx_hash: Option, diff --git a/src/serde_utils/base64_binary.rs b/src/serde_utils/base64_binary.rs index e4098b9..c8c42a4 100644 --- a/src/serde_utils/base64_binary.rs +++ b/src/serde_utils/base64_binary.rs @@ -3,15 +3,15 @@ use base64::Engine as _; use serde::Deserialize; pub fn serialize( - binaries: &Option>>, + blobs: &Option>>, serializer: S, ) -> Result where S: serde::Serializer, { - match binaries { - Some(binaries) => { - let base64_vec: Vec = binaries + match blobs { + Some(blobs) => { + let base64_vec: Vec = blobs .iter() .map(|binary| general_purpose::STANDARD.encode(binary)) .collect(); diff --git a/src/server/routes/transaction.rs b/src/server/routes/transaction.rs index 020737a..fef6d5e 100644 --- a/src/server/routes/transaction.rs +++ b/src/server/routes/transaction.rs @@ -26,7 +26,7 @@ pub struct SendTxRequest { #[serde(default)] pub tx_id: Option, #[serde(default, with = "crate::serde_utils::base64_binary")] - pub binaries: Option>>, + pub blobs: Option>>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -118,7 +118,7 @@ pub async fn send_tx( req.value, req.gas_limit, req.priority, - req.binaries, + req.blobs, api_token.relayer_id(), ) .await?; diff --git a/tests/send_too_many_txs.rs b/tests/send_too_many_txs.rs index 727dc46..d93b3fa 100644 --- a/tests/send_too_many_txs.rs +++ b/tests/send_too_many_txs.rs @@ -59,7 +59,7 @@ async fn send_too_many_txs() -> eyre::Result<()> { gas_limit: U256::from(21_000), priority: TransactionPriority::Regular, tx_id: None, - binaries: None, + blobs: None, }, ) .await?; @@ -76,7 +76,7 @@ async fn send_too_many_txs() -> eyre::Result<()> { gas_limit: U256::from(21_000), priority: TransactionPriority::Regular, tx_id: None, - binaries: None, + blobs: None, }, ) .await; @@ -104,7 +104,7 @@ async fn send_too_many_txs() -> eyre::Result<()> { gas_limit: U256::from(21_000), priority: TransactionPriority::Regular, tx_id: None, - binaries: None, + blobs: None, }, ) .await?; From 564e2341384b7978d703f1ff5a3dee8c5ccc4d13 Mon Sep 17 00:00:00 2001 From: Dawid Cichuta Date: Thu, 25 Apr 2024 15:22:21 +0200 Subject: [PATCH 3/3] Deserializer tests --- src/serde_utils/base64_binary.rs | 44 +++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/serde_utils/base64_binary.rs b/src/serde_utils/base64_binary.rs index c8c42a4..f39b7b4 100644 --- a/src/serde_utils/base64_binary.rs +++ b/src/serde_utils/base64_binary.rs @@ -33,15 +33,53 @@ where match base64_strings { Some(base64_vec) => { - let decoded_vec: Vec> = base64_vec + let decoded_vec: Result>, _> = base64_vec .into_iter() .map(|base64_str| { - general_purpose::STANDARD.decode(base64_str).unwrap() + general_purpose::STANDARD + .decode(base64_str) + .map_err(serde::de::Error::custom) }) .collect(); - Ok(Some(decoded_vec)) + Ok(Some(decoded_vec?)) } None => Ok(None), } } + +#[cfg(test)] +mod tests { + use serde::{Deserialize, Serialize}; + use serde_json; + + #[derive(Debug, Clone, Serialize, Deserialize)] + struct Test { + #[serde(with = "super")] + blobs: Option>>, + } + + #[test] + fn test_deserialize_with_valid_input() { + let blobs = + Some(["Hello", "world!"].map(|b| b.as_bytes().to_vec()).to_vec()); + let test = Test { + blobs: blobs.clone(), + }; + + let s = serde_json::to_string(&test).unwrap(); + + let test: Test = serde_json::from_str(&s).unwrap(); + assert_eq!(test.blobs, blobs); + } + + #[test] + fn test_deserialize_with_null_input() { + let test = Test { blobs: None }; + + let s = serde_json::to_string(&test).unwrap(); + + let test: Test = serde_json::from_str(&s).unwrap(); + assert_eq!(test.blobs, None); + } +}