From 3c8316f641facb940ebfcebb54d3a8de338312e2 Mon Sep 17 00:00:00 2001 From: Martin Fraga Date: Wed, 13 Mar 2024 12:12:45 -0300 Subject: [PATCH] feat: serialize fields as json (#165) * split input notes * make enum for notes tables * rename recipients variable and add more NoteTable uses * run fmt * change to_string for to_hex * correct nullable fields and reorder fields * remove unnecesarry note filter * remove unnecessary commit_height field * move related rows into metadata row for notes * remove dbg statements * switch to_string for to_hex * Revert "move related rows into metadata row for notes" This reverts commit bf46c0afbb74a0db44efdc69bccb0e8ee1418ea4. * restore filtering by pending notes * store grouped json fields * add json fields with serialization/deserialization I still have pending polishing the changes (abstracting some deserialization and removing debugs, etc.) and we should also add some kind of validation as well as having the proper NULL / NOT NULL columns (now everything is nullable) * remove dbg calls and set nullable/non-nullable cols accordingly * apply clippy suggestion * add helper functions to work with json values returned as strings * add documentation for handling json with sqlite * fix incorrect function use to fetch output notes * ignore doc tests * add json validity constraints * use structs to represent json columns * remove dbg statements * use proper serialization * use existing NoteMetadata instead of NoteRecordMetadata * also remove NoteRecordInclusionProof and use NoteInclusionProof instead --- src/errors.rs | 6 + src/store/mod.rs | 37 ++++- src/store/sqlite_store/mod.rs | 60 ++++++- src/store/sqlite_store/notes.rs | 258 ++++++++++++++++--------------- src/store/sqlite_store/store.sql | 78 ++++++++-- src/store/sqlite_store/sync.rs | 36 +++-- 6 files changed, 322 insertions(+), 153 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 8b9746231..b13739ace 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -204,6 +204,12 @@ impl From for StoreError { } } +impl From for StoreError { + fn from(value: NoteError) -> Self { + StoreError::NoteInclusionProofError(value) + } +} + impl From for StoreError { fn from(value: TransactionScriptError) -> Self { StoreError::TransactionScriptError(value) diff --git a/src/store/mod.rs b/src/store/mod.rs index fd9e63f7c..2580dd22a 100644 --- a/src/store/mod.rs +++ b/src/store/mod.rs @@ -14,7 +14,9 @@ use miden_objects::{ utils::collections::BTreeMap, BlockHeader, Digest, Word, }; + use miden_tx::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; +use serde::{Deserialize, Serialize}; pub mod data_store; pub mod sqlite_store; @@ -315,8 +317,8 @@ impl Deserializable for InputNoteRecord { fn read_from( source: &mut R, ) -> std::prelude::v1::Result { - let note: Note = source.read()?; - let proof: Option = source.read()?; + let note = Note::read_from(source)?; + let proof = Option::::read_from(source)?; Ok(InputNoteRecord::new(note, proof)) } } @@ -354,6 +356,37 @@ impl TryInto for InputNoteRecord { } } +#[derive(Serialize, Deserialize)] +struct NoteRecordDetails { + nullifier: String, + script: Vec, + inputs: Vec, + serial_num: Word, +} + +impl NoteRecordDetails { + fn new(nullifier: String, script: Vec, inputs: Vec, serial_num: Word) -> Self { + Self { + nullifier, + script, + inputs, + serial_num, + } + } + + fn script(&self) -> &Vec { + &self.script + } + + fn inputs(&self) -> &Vec { + &self.inputs + } + + fn serial_num(&self) -> &Word { + &self.serial_num + } +} + // CHAIN MMR NODE FILTER // ================================================================================================ diff --git a/src/store/sqlite_store/mod.rs b/src/store/sqlite_store/mod.rs index d610eaf4c..5f97ab3d4 100644 --- a/src/store/sqlite_store/mod.rs +++ b/src/store/sqlite_store/mod.rs @@ -26,7 +26,65 @@ mod transactions; // SQLITE STORE // ================================================================================================ - +/// +/// Represents a connection with an sqlite database +/// +/// +/// Current table definitions can be found at `store.sql` migration file. One particular column +/// type used is JSON, for which you can look more info at [sqlite's official documentation](https://www.sqlite.org/json1.html). +/// In the case of json, some caveats must be taken: +/// +/// - To insert json values you must use sqlite's `json` function in the query alongside named +/// parameters, and the provided parameter must be a valid json. That is: +/// +/// ```sql +/// INSERT INTO SOME_TABLE +/// (some_field) +/// VALUES (json(:some_field))") +/// ``` +/// +/// ```ignore +/// let metadata = format!(r#"{{"some_inner_field": {some_field}, "some_other_inner_field": {some_other_field}}}"#); +/// ``` +/// +/// (Using raw string literals for the jsons is encouraged if possible) +/// +/// - To get data from any of the json fields you can use the `json_extract` function (in some +/// cases you'll need to do some explicit type casting to help rusqlite figure out types): +/// +/// ```sql +/// SELECT CAST(json_extract(some_json_col, '$.some_json_field') AS TEXT) from some_table +/// ``` +/// +/// - For some datatypes you'll need to do some manual serialization/deserialization. For example, +/// suppose one of your json fields is an array of digests. Then you'll need to +/// - Create the json with an array of strings representing the digests: +/// +/// ```ignore +/// let some_array_field = some_array +/// .into_iter() +/// .map(array_elem_to_string) +/// .collect::>() +/// .join(","); +/// +/// Some(format!( +/// r#"{{ +/// "some_array_field": [{some_array_field}] +/// }}"# +/// )), +/// ``` +/// +/// - When deserializing, handling the extra symbols (`[`, `]`, `,`, `"`). For that you can use +/// the `parse_json_array` function: +/// +/// ```ignore +/// let some_array = parse_json_array(some_array_field) +/// .into_iter() +/// .map(parse_json_byte_str) +/// .collect::, _>>()?; +/// ``` +/// - Thus, if needed you can create a struct representing the json values and use serde_json to +/// simplify all of the serialization/deserialization logic pub struct SqliteStore { pub(crate) db: Connection, } diff --git a/src/store/sqlite_store/notes.rs b/src/store/sqlite_store/notes.rs index 1a9cce361..5e83623dd 100644 --- a/src/store/sqlite_store/notes.rs +++ b/src/store/sqlite_store/notes.rs @@ -1,25 +1,27 @@ use std::fmt; use crate::errors::StoreError; -use crate::store::{InputNoteRecord, NoteFilter}; +use crate::store::{InputNoteRecord, NoteFilter, NoteRecordDetails}; use super::SqliteStore; use clap::error::Result; use miden_objects::{ - accounts::AccountId, - notes::{Note, NoteAssets, NoteId, NoteInclusionProof, NoteInputs, NoteMetadata, NoteScript}, - Felt, + notes::{ + Note, NoteAssets, NoteId, NoteInclusionProof, NoteInputs, NoteMetadata, NoteScript, + Nullifier, + }, + Digest, }; use miden_tx::utils::{Deserializable, Serializable}; -use rusqlite::{params, Transaction}; +use rusqlite::{named_params, params, Transaction}; fn insert_note_query(table_name: NoteTable) -> String { format!("\ INSERT INTO {table_name} - (note_id, nullifier, script, assets, inputs, serial_num, sender_id, tag, inclusion_proof, recipient, status) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + (note_id, assets, recipient, status, metadata, details, inclusion_proof) + VALUES (:note_id, :assets, :recipient, :status, json(:metadata), json(:details), json(:inclusion_proof))") } // TYPES @@ -27,19 +29,15 @@ fn insert_note_query(table_name: NoteTable) -> String { type SerializedInputNoteData = ( String, - String, - Vec, - Vec, Vec, String, - i64, - i64, - Option>, String, String, + String, + Option, ); -type SerializedInputNoteParts = (Vec, Vec, Vec, String, u64, u64, Option>); +type SerializedInputNoteParts = (Vec, String, String, Option); // NOTE TABLE // ================================================================================================ @@ -65,7 +63,15 @@ impl fmt::Display for NoteTable { impl NoteFilter { /// Returns a [String] containing the query for this Filter fn to_query(&self, notes_table: NoteTable) -> String { - let base = format!("SELECT script, inputs, assets, serial_num, sender_id, tag, inclusion_proof FROM {notes_table}"); + let base = format!( + "SELECT + assets, + details, + metadata, + inclusion_proof + from {notes_table}" + ); + match self { NoteFilter::All => base, NoteFilter::Committed => format!("{base} WHERE status = 'committed'"), @@ -106,7 +112,13 @@ impl SqliteStore { pub(crate) fn get_input_note(&self, note_id: NoteId) -> Result { let query_id = ¬e_id.inner().to_string(); - const QUERY: &str = "SELECT script, inputs, assets, serial_num, sender_id, tag, inclusion_proof FROM input_notes WHERE note_id = ?"; + + const QUERY: &str = "SELECT + assets, + details, + metadata, + inclusion_proof + from input_notes WHERE note_id = ?"; self.db .prepare(QUERY)? @@ -123,6 +135,26 @@ impl SqliteStore { Ok(tx.commit()?) } + + /// Returns the nullifiers of all unspent input notes + pub fn get_unspent_input_note_nullifiers(&self) -> Result, StoreError> { + const QUERY: &str = "SELECT json_extract(details, '$.nullifier') FROM input_notes WHERE status = 'committed'"; + + self.db + .prepare(QUERY)? + .query_map([], |row| row.get(0)) + .expect("no binding parameters used in query") + .map(|result| { + result + .map_err(|err| StoreError::ParsingError(err.to_string())) + .and_then(|v: String| { + Digest::try_from(v) + .map(Nullifier::from) + .map_err(StoreError::HexParseError) + }) + }) + .collect::, _>>() + } } // HELPERS @@ -133,35 +165,20 @@ pub(super) fn insert_input_note_tx( tx: &Transaction<'_>, note: &InputNoteRecord, ) -> Result<(), StoreError> { - let ( - note_id, - nullifier, - script, - vault, - inputs, - serial_num, - sender_id, - tag, - inclusion_proof, - recipient, - status, - ) = serialize_note(note)?; + let (note_id, assets, recipient, status, metadata, details, inclusion_proof) = + serialize_note(note)?; tx.execute( &insert_note_query(NoteTable::InputNotes), - params![ - note_id, - nullifier, - script, - vault, - inputs, - serial_num, - sender_id, - tag, - inclusion_proof, - recipient, - status, - ], + named_params! { + ":note_id": note_id, + ":assets": assets, + ":recipient": recipient, + ":status": status, + ":metadata": metadata, + ":details": details, + ":inclusion_proof": inclusion_proof, + }, ) .map_err(|err| StoreError::QueryError(err.to_string())) .map(|_| ()) @@ -172,35 +189,20 @@ pub fn insert_output_note_tx( tx: &Transaction<'_>, note: &InputNoteRecord, ) -> Result<(), StoreError> { - let ( - note_id, - nullifier, - script, - vault, - inputs, - serial_num, - sender_id, - tag, - inclusion_proof, - recipient, - status, - ) = serialize_note(note)?; + let (note_id, assets, recipient, status, metadata, details, inclusion_proof) = + serialize_note(note)?; tx.execute( &insert_note_query(NoteTable::OutputNotes), - params![ - note_id, - nullifier, - script, - vault, - inputs, - serial_num, - sender_id, - tag, - inclusion_proof, - recipient, - status, - ], + named_params! { + ":note_id": note_id, + ":assets": assets, + ":recipient": recipient, + ":status": status, + ":metadata": metadata, + ":details": details, + ":inclusion_proof": inclusion_proof, + }, ) .map_err(|err| StoreError::QueryError(err.to_string())) .map(|_| ()) @@ -210,44 +212,44 @@ pub fn insert_output_note_tx( fn parse_input_note_columns( row: &rusqlite::Row<'_>, ) -> Result { - let script: Vec = row.get(0)?; - let inputs: Vec = row.get(1)?; - let vault: Vec = row.get(2)?; - let serial_num: String = row.get(3)?; - let sender_id = row.get::(4)? as u64; - let tag = row.get::(5)? as u64; - let inclusion_proof: Option> = row.get(6)?; - Ok(( - script, - inputs, - vault, - serial_num, - sender_id, - tag, - inclusion_proof, - )) + let assets: Vec = row.get(0)?; + let details: String = row.get(1)?; + let metadata: String = row.get(2)?; + let inclusion_proof: Option = row.get(3)?; + + Ok((assets, details, metadata, inclusion_proof)) } /// Parse a note from the provided parts. fn parse_input_note( serialized_input_note_parts: SerializedInputNoteParts, ) -> Result { - let (script, inputs, note_assets, serial_num, sender_id, tag, inclusion_proof) = + let (note_assets, note_details, note_metadata, note_inclusion_proof) = serialized_input_note_parts; - let script = NoteScript::read_from_bytes(&script)?; - let inputs = NoteInputs::read_from_bytes(&inputs)?; - let vault = NoteAssets::read_from_bytes(¬e_assets)?; - let serial_num = - serde_json::from_str(&serial_num).map_err(StoreError::JsonDataDeserializationError)?; - let note_metadata = NoteMetadata::new( - AccountId::new_unchecked(Felt::new(sender_id)), - Felt::new(tag), - ); - let note = Note::from_parts(script, inputs, vault, serial_num, note_metadata); - - let inclusion_proof = inclusion_proof - .map(|proof| NoteInclusionProof::read_from_bytes(&proof)) - .transpose()?; + + let note_details: NoteRecordDetails = + serde_json::from_str(¬e_details).map_err(StoreError::JsonDataDeserializationError)?; + let note_metadata: NoteMetadata = + serde_json::from_str(¬e_metadata).map_err(StoreError::JsonDataDeserializationError)?; + + let script = NoteScript::read_from_bytes(note_details.script())?; + let inputs = NoteInputs::read_from_bytes(note_details.inputs())?; + + let serial_num = note_details.serial_num(); + let note_metadata = NoteMetadata::new(note_metadata.sender(), note_metadata.tag()); + let note_assets = NoteAssets::read_from_bytes(¬e_assets)?; + let note = Note::from_parts(script, inputs, note_assets, *serial_num, note_metadata); + + let inclusion_proof = match note_inclusion_proof { + Some(note_inclusion_proof) => { + let note_inclusion_proof: NoteInclusionProof = + serde_json::from_str(¬e_inclusion_proof) + .map_err(StoreError::JsonDataDeserializationError)?; + + Some(note_inclusion_proof) + } + _ => None, + }; Ok(InputNoteRecord::new(note, inclusion_proof)) } @@ -257,56 +259,58 @@ pub(crate) fn serialize_note( note: &InputNoteRecord, ) -> Result { let note_id = note.note_id().inner().to_string(); - let nullifier = note.note().nullifier().inner().to_string(); - let script = note.note().script().to_bytes(); let note_assets = note.note().assets().to_bytes(); - let inputs = note.note().inputs().to_bytes(); - let serial_num = serde_json::to_string(¬e.note().serial_num()) - .map_err(StoreError::InputSerializationError)?; - let sender_id = u64::from(note.note().metadata().sender()) as i64; - let tag = u64::from(note.note().metadata().tag()) as i64; let (inclusion_proof, status) = match note.inclusion_proof() { Some(proof) => { // FIXME: This removal is to accomodate a problem with how the node constructs paths where // they are constructed using note ID instead of authentication hash, so for now we remove the first // node here. // + // Note: once removed we can also stop creating a new `NoteInclusionProof` + // // See: https://github.com/0xPolygonMiden/miden-node/blob/main/store/src/state.rs#L274 let mut path = proof.note_path().clone(); if path.len() > 0 { let _removed = path.remove(0); } - ( - Some( - NoteInclusionProof::new( - proof.origin().block_num, - proof.sub_hash(), - proof.note_root(), - proof.origin().node_index.value(), - path, - ) - .map_err(StoreError::NoteInclusionProofError)? - .to_bytes(), - ), - String::from("committed"), - ) + let block_num = proof.origin().block_num; + let node_index = proof.origin().node_index.value(); + let sub_hash = proof.sub_hash(); + let note_root = proof.note_root(); + + let inclusion_proof = serde_json::to_string(&NoteInclusionProof::new( + block_num, sub_hash, note_root, node_index, path, + )?) + .map_err(StoreError::InputSerializationError)?; + + (Some(inclusion_proof), String::from("committed")) } None => (None, String::from("pending")), }; let recipient = note.note().recipient().to_hex(); + let sender_id = note.note().metadata().sender(); + let tag = note.note().metadata().tag(); + let metadata = serde_json::to_string(&NoteMetadata::new(sender_id, tag)) + .map_err(StoreError::InputSerializationError)?; + + let nullifier = note.note().nullifier().inner().to_string(); + let script = note.note().script().to_bytes(); + let inputs = note.note().inputs().to_bytes(); + let serial_num = note.note().serial_num(); + let details = serde_json::to_string(&NoteRecordDetails::new( + nullifier, script, inputs, serial_num, + )) + .map_err(StoreError::InputSerializationError)?; + Ok(( note_id, - nullifier, - script, note_assets, - inputs, - serial_num, - sender_id, - tag, - inclusion_proof, recipient, status, + metadata, + details, + inclusion_proof, )) } diff --git a/src/store/sqlite_store/store.sql b/src/store/sqlite_store/store.sql index 502f1ca48..f9f881331 100644 --- a/src/store/sqlite_store/store.sql +++ b/src/store/sqlite_store/store.sql @@ -70,37 +70,87 @@ CREATE TABLE transaction_scripts ( -- Create input notes table CREATE TABLE input_notes ( note_id BLOB NOT NULL, -- the note id - nullifier BLOB NOT NULL, -- the nullifier of the note recipient BLOB NOT NULL, -- the note recipient - script BLOB NOT NULL, -- the serialized NoteScript, including script hash and ProgramAst assets BLOB NOT NULL, -- the serialized NoteAssets, including vault hash and list of assets - inputs BLOB NOT NULL, -- the serialized NoteInputs, including inputs hash and list of inputs - serial_num BLOB NOT NULL, -- the note serial number. - sender_id UNSIGNED BIG INT NULL, -- the account ID of the sender. Known once the note is recorded on chain - tag UNSIGNED BIG INT NULL, -- the note tag. Known once the note is recorded on-chain - inclusion_proof BLOB NULL, -- the inclusion proof of the note against a block number. Known once the note is recorded on-chain status TEXT CHECK( status IN ( -- the status of the note - either pending, committed or consumed 'pending', 'committed', 'consumed' )), + + inclusion_proof JSON NULL, -- JSON consisting of the following fields: + -- block_num -- number of the block the note was included in + -- note_index -- the index of the note in the note Merkle tree of the block the note was created in. + -- sub_hash -- sub hash of the block the note was included in stored as a hex string + -- note_root -- the note root of the block the note was created in + -- note_path -- the Merkle path to the note in the note Merkle tree of the block the note was created in, stored as an array of digests + + metadata JSON NULL, -- JSON consisting of the following fields: + -- sender_id -- the account ID of the sender + -- tag -- the note tag + + details JSON NOT NULL, -- JSON consisting of the following fields: + -- nullifier -- the nullifier of the note + -- script -- the serialized NoteScript, including script hash and ProgramAst + -- inputs -- the serialized NoteInputs, including inputs hash and list of inputs + -- serial_num -- the note serial number PRIMARY KEY (note_id) + + CONSTRAINT check_valid_inclusion_proof_json CHECK ( + inclusion_proof IS NULL OR + ( + json_extract(inclusion_proof, '$.origin.block_num') IS NOT NULL AND + json_extract(inclusion_proof, '$.origin.node_index') IS NOT NULL AND + json_extract(inclusion_proof, '$.sub_hash') IS NOT NULL AND + json_extract(inclusion_proof, '$.note_root') IS NOT NULL AND + json_extract(inclusion_proof, '$.note_path') IS NOT NULL + )) + CONSTRAINT check_valid_metadata_json CHECK (metadata IS NULL OR (json_extract(metadata, '$.sender') IS NOT NULL AND json_extract(metadata, '$.tag') IS NOT NULL)) ); -- Create output notes table CREATE TABLE output_notes ( note_id BLOB NOT NULL, -- the note id - nullifier BLOB NULL, -- the nullifier of the note, only known if we know script, inputs, serial_num recipient BLOB NOT NULL, -- the note recipient - script BLOB NULL, -- the serialized NoteScript, including script hash and ProgramAst. May not be known assets BLOB NOT NULL, -- the serialized NoteAssets, including vault hash and list of assets - inputs BLOB NULL, -- the serialized NoteInputs, including inputs hash and list of inputs. May not be known - serial_num BLOB NULL, -- the note serial number. May not be known - sender_id UNSIGNED BIG INT NOT NULL, -- the account ID of the sender - tag UNSIGNED BIG INT NOT NULL, -- the note tag - inclusion_proof BLOB NULL, -- the inclusion proof of the note against a block number status TEXT CHECK( status IN ( -- the status of the note - either pending, committed or consumed 'pending', 'committed', 'consumed' )), + + inclusion_proof JSON NULL, -- JSON consisting of the following fields: + -- block_num -- number of the block the note was included in + -- note_index -- the index of the note in the note Merkle tree of the block the note was created in. + -- sub_hash -- sub hash of the block the note was included in stored as a hex string + -- note_root -- the note root of the block the note was created in + -- note_path -- the Merkle path to the note in the note Merkle tree of the block the note was created in, stored as an array of digests + + metadata JSON NOT NULL, -- JSON consisting of the following fields: + -- sender_id -- the account ID of the sender + -- tag -- the note tag + + details JSON NULL, -- JSON consisting of the following fields: + -- nullifier -- the nullifier of the note + -- script -- the serialized NoteScript, including script hash and ProgramAst + -- inputs -- the serialized NoteInputs, including inputs hash and list of inputs + -- serial_num -- the note serial number PRIMARY KEY (note_id) + + CONSTRAINT check_valid_inclusion_proof_json CHECK ( + inclusion_proof IS NULL OR + ( + json_extract(inclusion_proof, '$.origin.block_num') IS NOT NULL AND + json_extract(inclusion_proof, '$.origin.node_index') IS NOT NULL AND + json_extract(inclusion_proof, '$.sub_hash') IS NOT NULL AND + json_extract(inclusion_proof, '$.note_root') IS NOT NULL AND + json_extract(inclusion_proof, '$.note_path') IS NOT NULL + )) + CONSTRAINT check_valid_details_json CHECK ( + details IS NULL OR + ( + json_extract(details, '$.nullifier') IS NOT NULL AND + json_extract(details, '$.script') IS NOT NULL AND + json_extract(details, '$.inputs') IS NOT NULL AND + json_extract(details, '$.serial_num') IS NOT NULL + )) + ); -- Create state sync table diff --git a/src/store/sqlite_store/sync.rs b/src/store/sqlite_store/sync.rs index d194dc597..24aa79e17 100644 --- a/src/store/sqlite_store/sync.rs +++ b/src/store/sqlite_store/sync.rs @@ -5,8 +5,7 @@ use miden_objects::{ transaction::TransactionId, BlockHeader, Digest, }; -use miden_tx::utils::Serializable; -use rusqlite::params; +use rusqlite::{named_params, params}; use super::SqliteStore; @@ -73,12 +72,12 @@ impl SqliteStore { // Update spent notes for nullifier in nullifiers.iter() { const SPENT_INPUT_NOTE_QUERY: &str = - "UPDATE input_notes SET status = 'consumed' WHERE nullifier = ?"; + "UPDATE input_notes SET status = 'consumed' WHERE json_extract(details, '$.nullifier') = ?"; let nullifier = nullifier.to_hex(); tx.execute(SPENT_INPUT_NOTE_QUERY, params![nullifier])?; const SPENT_OUTPUT_NOTE_QUERY: &str = - "UPDATE output_notes SET status = 'consumed' WHERE nullifier = ?"; + "UPDATE output_notes SET status = 'consumed' WHERE json_extract(details, '$.nullifier') = ?"; tx.execute(SPENT_OUTPUT_NOTE_QUERY, params![nullifier])?; } @@ -92,22 +91,41 @@ impl SqliteStore { // Update tracked notes for (note_id, inclusion_proof) in committed_notes.iter() { + let block_num = inclusion_proof.origin().block_num; + let sub_hash = inclusion_proof.sub_hash(); + let note_root = inclusion_proof.note_root(); + let note_index = inclusion_proof.origin().node_index.value(); + + let inclusion_proof = serde_json::to_string(&NoteInclusionProof::new( + block_num, + sub_hash, + note_root, + note_index, + inclusion_proof.note_path().clone(), + )?) + .map_err(StoreError::InputSerializationError)?; + const COMMITTED_INPUT_NOTES_QUERY: &str = - "UPDATE input_notes SET status = 'committed', inclusion_proof = ? WHERE note_id = ?"; + "UPDATE input_notes SET status = 'committed', inclusion_proof = json(:inclusion_proof) WHERE note_id = :note_id"; - let inclusion_proof = Some(inclusion_proof.to_bytes()); tx.execute( COMMITTED_INPUT_NOTES_QUERY, - params![inclusion_proof, note_id.inner().to_hex()], + named_params! { + ":inclusion_proof": inclusion_proof, + ":note_id": note_id.inner().to_hex() + }, )?; // Update output notes const COMMITTED_OUTPUT_NOTES_QUERY: &str = - "UPDATE output_notes SET status = 'committed', inclusion_proof = ? WHERE note_id = ?"; + "UPDATE output_notes SET status = 'committed', inclusion_proof = json(:inclusion_proof) WHERE note_id = :note_id"; tx.execute( COMMITTED_OUTPUT_NOTES_QUERY, - params![inclusion_proof, note_id.inner().to_hex()], + named_params! { + ":inclusion_proof": inclusion_proof, + ":note_id": note_id.inner().to_hex() + }, )?; }