Skip to content

Commit

Permalink
feat: sender and receiver protocols use bytes (not hex string) in wal…
Browse files Browse the repository at this point in the history
…let database (#5950)

Description
---
Changed the sender and receiver protocol in the wallet database to use
bytes instead of a hex string, as the underlying data type is encrypted
bytes. This issue was highlighted due to the to_hex function in
tari_utilities not being able to convert large transactions into hex
strings (returned **String to large**) for saving in the wallet
database.

Motivation and Context
---
See above.

How Has This Been Tested?
---
Existing unit tests and cucumber tests passed
Added a new integration-level unit test `async fn
large_interactive_transaction()` to test the conversion from pending
outgoing and incoming transactions to and from the database.

What process can a PR reviewer use to test or verify this change?
---
Code walk-through
Run the new unit test

<!-- Checklist -->
<!-- 1. Is the title of your PR in the form that would make nice release
notes? The title, excluding the conventional commit
tag, will be included exactly as is in the CHANGELOG, so please think
about it carefully. -->

Breaking Changes
---

- [x] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [ ] Other - Please specify

<!-- Does this include a breaking change? If so, include this line as a
footer -->
<!-- BREAKING CHANGE: Description what the user should do, e.g. delete a
database, resync the chain -->

Co-authored-by: SW van Heerden <swvheerden@gmail.com>
  • Loading branch information
hansieodendaal and SWvheerden committed Nov 13, 2023
1 parent 4870c49 commit 4cbdfec
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 58 deletions.
@@ -0,0 +1 @@
-- This file should undo anything in `up.sql`
@@ -0,0 +1,36 @@
-- Any old 'inbound_transactions' will not be valid due to the change in 'receiver_protocol' to 'BLOB', so we drop and
-- recreate the table.

DROP TABLE inbound_transactions;
CREATE TABLE inbound_transactions
(
tx_id BIGINT PRIMARY KEY NOT NULL,
source_address BLOB NOT NULL,
amount BIGINT NOT NULL,
receiver_protocol BLOB NOT NULL,
message TEXT NOT NULL,
timestamp DATETIME NOT NULL,
cancelled INTEGER NOT NULL,
direct_send_success INTEGER DEFAULT 0 NOT NULL,
send_count INTEGER DEFAULT 0 NOT NULL,
last_send_timestamp DATETIME NULL
);

-- Any old 'outbound_transactions' will not be valid due to the change in 'sender_protocol' to 'BLOB', so we drop and
-- recreate the table.

DROP TABLE outbound_transactions;
CREATE TABLE outbound_transactions
(
tx_id BIGINT PRIMARY KEY NOT NULL,
destination_address BLOB NOT NULL,
amount BIGINT NOT NULL,
fee BIGINT NOT NULL,
sender_protocol BLOB NOT NULL,
message TEXT NOT NULL,
timestamp DATETIME NOT NULL,
cancelled INTEGER DEFAULT 0 NOT NULL,
direct_send_success INTEGER DEFAULT 0 NOT NULL,
send_count INTEGER DEFAULT 0 NOT NULL,
last_send_timestamp DATETIME NULL
);
26 changes: 14 additions & 12 deletions base_layer/wallet/src/schema.rs
@@ -1,4 +1,6 @@
table! {
// @generated automatically by Diesel CLI.

diesel::table! {
burnt_proofs (id) {
id -> Integer,
reciprocal_claim_public_key -> Text,
Expand All @@ -7,14 +9,14 @@ table! {
}
}

table! {
diesel::table! {
client_key_values (key) {
key -> Text,
value -> Text,
}
}

table! {
diesel::table! {
completed_transactions (tx_id) {
tx_id -> BigInt,
source_address -> Binary,
Expand All @@ -39,12 +41,12 @@ table! {
}
}

table! {
diesel::table! {
inbound_transactions (tx_id) {
tx_id -> BigInt,
source_address -> Binary,
amount -> BigInt,
receiver_protocol -> Text,
receiver_protocol -> Binary,
message -> Text,
timestamp -> Timestamp,
cancelled -> Integer,
Expand All @@ -54,7 +56,7 @@ table! {
}
}

table! {
diesel::table! {
known_one_sided_payment_scripts (script_hash) {
script_hash -> Binary,
private_key -> Text,
Expand All @@ -64,13 +66,13 @@ table! {
}
}

table! {
diesel::table! {
outbound_transactions (tx_id) {
tx_id -> BigInt,
destination_address -> Binary,
amount -> BigInt,
fee -> BigInt,
sender_protocol -> Text,
sender_protocol -> Binary,
message -> Text,
timestamp -> Timestamp,
cancelled -> Integer,
Expand All @@ -80,7 +82,7 @@ table! {
}
}

table! {
diesel::table! {
outputs (id) {
id -> Integer,
commitment -> Binary,
Expand Down Expand Up @@ -120,7 +122,7 @@ table! {
}
}

table! {
diesel::table! {
scanned_blocks (header_hash) {
header_hash -> Binary,
height -> BigInt,
Expand All @@ -130,14 +132,14 @@ table! {
}
}

table! {
diesel::table! {
wallet_settings (key) {
key -> Text,
value -> Text,
}
}

allow_tables_to_appear_in_same_query!(
diesel::allow_tables_to_appear_in_same_query!(
burnt_proofs,
client_key_values,
completed_transactions,
Expand Down
2 changes: 2 additions & 0 deletions base_layer/wallet/src/transaction_service/error.rs
Expand Up @@ -231,6 +231,8 @@ pub enum TransactionStorageError {
UnexpectedResult(String),
#[error("Bincode error: `{0}`")]
BincodeSerialize(String),
#[error("Bincode error: `{0}`")]
BincodeDeserialize(String),
#[error("This write operation is not supported for provided DbKey")]
OperationNotSupported,
#[error("Could not find all values specified for batch operation")]
Expand Down
69 changes: 23 additions & 46 deletions base_layer/wallet/src/transaction_service/storage/sqlite_db.rs
Expand Up @@ -23,7 +23,6 @@
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
str::from_utf8,
sync::{Arc, RwLock},
};

Expand All @@ -45,11 +44,7 @@ use tari_common_types::{
types::{BlockHash, PrivateKey, PublicKey, Signature},
};
use tari_core::transactions::tari_amount::MicroMinotari;
use tari_utilities::{
hex::{from_hex, Hex},
ByteArray,
Hidden,
};
use tari_utilities::{ByteArray, Hidden};
use thiserror::Error;
use tokio::time::Instant;
use zeroize::Zeroize;
Expand Down Expand Up @@ -1166,7 +1161,7 @@ struct InboundTransactionSql {
tx_id: i64,
source_address: Vec<u8>,
amount: i64,
receiver_protocol: String,
receiver_protocol: Vec<u8>,
message: String,
timestamp: NaiveDateTime,
cancelled: i32,
Expand Down Expand Up @@ -1345,11 +1340,13 @@ impl InboundTransactionSql {
}

fn try_from(i: InboundTransaction, cipher: &XChaCha20Poly1305) -> Result<Self, TransactionStorageError> {
let receiver_protocol_bytes = bincode::serialize(&i.receiver_protocol)
.map_err(|e| TransactionStorageError::BincodeSerialize(e.to_string()))?;
let i = Self {
tx_id: i.tx_id.as_u64() as i64,
source_address: i.source_address.to_bytes().to_vec(),
amount: u64::from(i.amount) as i64,
receiver_protocol: serde_json::to_string(&i.receiver_protocol)?,
receiver_protocol: receiver_protocol_bytes.to_vec(),
message: i.message,
timestamp: i.timestamp,
cancelled: i32::from(i.cancelled),
Expand All @@ -1376,26 +1373,15 @@ impl Encryptable<XChaCha20Poly1305> for InboundTransactionSql {
self.receiver_protocol = encrypt_bytes_integral_nonce(
cipher,
self.domain("receiver_protocol"),
Hidden::hide(self.receiver_protocol.as_bytes().to_vec()),
)?
.to_hex();
Hidden::hide(self.receiver_protocol),
)?;

Ok(self)
}

fn decrypt(mut self, cipher: &XChaCha20Poly1305) -> Result<Self, String> {
let mut decrypted_protocol = decrypt_bytes_integral_nonce(
cipher,
self.domain("receiver_protocol"),
&from_hex(self.receiver_protocol.as_str()).map_err(|e| e.to_string())?,
)?;

self.receiver_protocol = from_utf8(decrypted_protocol.as_slice())
.map_err(|e| e.to_string())?
.to_string();

// zeroize sensitive data
decrypted_protocol.zeroize();
self.receiver_protocol =
decrypt_bytes_integral_nonce(cipher, self.domain("receiver_protocol"), &self.receiver_protocol)?;

Ok(self)
}
Expand All @@ -1408,7 +1394,8 @@ impl InboundTransaction {
tx_id: (i.tx_id as u64).into(),
source_address: TariAddress::from_bytes(&i.source_address).map_err(TransactionKeyError::Source)?,
amount: MicroMinotari::from(i.amount as u64),
receiver_protocol: serde_json::from_str(&i.receiver_protocol.clone())?,
receiver_protocol: bincode::deserialize(&i.receiver_protocol)
.map_err(|e| TransactionStorageError::BincodeDeserialize(e.to_string()))?,
status: TransactionStatus::Pending,
message: i.message,
timestamp: i.timestamp,
Expand All @@ -1425,7 +1412,7 @@ impl InboundTransaction {
pub struct UpdateInboundTransactionSql {
cancelled: Option<i32>,
direct_send_success: Option<i32>,
receiver_protocol: Option<String>,
receiver_protocol: Option<Vec<u8>>,
send_count: Option<i32>,
last_send_timestamp: Option<Option<NaiveDateTime>>,
}
Expand All @@ -1438,7 +1425,7 @@ struct OutboundTransactionSql {
destination_address: Vec<u8>,
amount: i64,
fee: i64,
sender_protocol: String,
sender_protocol: Vec<u8>,
message: String,
timestamp: NaiveDateTime,
cancelled: i32,
Expand Down Expand Up @@ -1601,12 +1588,14 @@ impl OutboundTransactionSql {
}

fn try_from(o: OutboundTransaction, cipher: &XChaCha20Poly1305) -> Result<Self, TransactionStorageError> {
let sender_protocol_bytes = bincode::serialize(&o.sender_protocol)
.map_err(|e| TransactionStorageError::BincodeSerialize(e.to_string()))?;
let outbound_tx = Self {
tx_id: o.tx_id.as_u64() as i64,
destination_address: o.destination_address.to_bytes().to_vec(),
amount: u64::from(o.amount) as i64,
fee: u64::from(o.fee) as i64,
sender_protocol: serde_json::to_string(&o.sender_protocol)?,
sender_protocol: sender_protocol_bytes.to_vec(),
message: o.message,
timestamp: o.timestamp,
cancelled: i32::from(o.cancelled),
Expand Down Expand Up @@ -1634,42 +1623,30 @@ impl Encryptable<XChaCha20Poly1305> for OutboundTransactionSql {
self.sender_protocol = encrypt_bytes_integral_nonce(
cipher,
self.domain("sender_protocol"),
Hidden::hide(self.sender_protocol.as_bytes().to_vec()),
)?
.to_hex();
Hidden::hide(self.sender_protocol),
)?;

Ok(self)
}

fn decrypt(mut self, cipher: &XChaCha20Poly1305) -> Result<Self, String> {
let mut decrypted_protocol = decrypt_bytes_integral_nonce(
cipher,
self.domain("sender_protocol"),
&from_hex(self.sender_protocol.as_str()).map_err(|e| e.to_string())?,
)?;

self.sender_protocol = from_utf8(decrypted_protocol.as_slice())
.map_err(|e| e.to_string())?
.to_string();

// zeroize sensitive data
decrypted_protocol.zeroize();

self.sender_protocol =
decrypt_bytes_integral_nonce(cipher, self.domain("sender_protocol"), &self.sender_protocol)?;
Ok(self)
}
}

impl OutboundTransaction {
fn try_from(o: OutboundTransactionSql, cipher: &XChaCha20Poly1305) -> Result<Self, TransactionStorageError> {
let mut o = o.decrypt(cipher).map_err(TransactionStorageError::AeadError)?;

let outbound_tx = Self {
tx_id: (o.tx_id as u64).into(),
destination_address: TariAddress::from_bytes(&o.destination_address)
.map_err(TransactionKeyError::Destination)?,
amount: MicroMinotari::from(o.amount as u64),
fee: MicroMinotari::from(o.fee as u64),
sender_protocol: serde_json::from_str(&o.sender_protocol.clone())?,
sender_protocol: bincode::deserialize(&o.sender_protocol)
.map_err(|e| TransactionStorageError::BincodeDeserialize(e.to_string()))?,
status: TransactionStatus::Pending,
message: o.message,
timestamp: o.timestamp,
Expand All @@ -1691,7 +1668,7 @@ impl OutboundTransaction {
pub struct UpdateOutboundTransactionSql {
cancelled: Option<i32>,
direct_send_success: Option<i32>,
sender_protocol: Option<String>,
sender_protocol: Option<Vec<u8>>,
send_count: Option<i32>,
last_send_timestamp: Option<Option<NaiveDateTime>>,
}
Expand Down

0 comments on commit 4cbdfec

Please sign in to comment.