diff --git a/db/migrations/004_transactions_binaries.sql b/db/migrations/004_transactions_binaries.sql new file mode 100644 index 0000000..3d9bc38 --- /dev/null +++ b/db/migrations/004_transactions_binaries.sql @@ -0,0 +1 @@ +ALTER TABLE transactions ADD COLUMN blobs BYTEA[] diff --git a/src/db.rs b/src/db.rs index ec2b221..c53cb58 100644 --- a/src/db.rs +++ b/src/db.rs @@ -263,6 +263,7 @@ impl Database { value: U256, gas_limit: U256, priority: TransactionPriority, + blobs: 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, blobs) + 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(blobs) .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.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) @@ -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.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 @@ -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.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 @@ -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. 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 @@ -1405,12 +1407,13 @@ mod tests { let value = U256::from(0); let gas_limit = U256::from(0); let priority = TransactionPriority::Regular; + 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, relayer_id, + tx_id, to, data, value, gas_limit, priority, blobs, 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.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 b9113be..8720a6a 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 blobs: 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 blobs: 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 blobs: 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..f39b7b4 --- /dev/null +++ b/src/serde_utils/base64_binary.rs @@ -0,0 +1,85 @@ +use base64::engine::general_purpose; +use base64::Engine as _; +use serde::Deserialize; + +pub fn serialize( + blobs: &Option>>, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + match blobs { + Some(blobs) => { + let base64_vec: Vec = blobs + .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: Result>, _> = base64_vec + .into_iter() + .map(|base64_str| { + general_purpose::STANDARD + .decode(base64_str) + .map_err(serde::de::Error::custom) + }) + .collect(); + + 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); + } +} diff --git a/src/server/routes/transaction.rs b/src/server/routes/transaction.rs index 45e16d5..fef6d5e 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 blobs: Option>>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -116,6 +118,7 @@ pub async fn send_tx( req.value, req.gas_limit, req.priority, + req.blobs, api_token.relayer_id(), ) .await?; diff --git a/tests/send_too_many_txs.rs b/tests/send_too_many_txs.rs index 226cc43..d93b3fa 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, + blobs: 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, + blobs: 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, + blobs: None, }, ) .await?;