Skip to content

Commit

Permalink
Adding Custom Notes and Custom Transactions to Web SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
Dennis Garcia committed Sep 10, 2024
1 parent 801c0e4 commit f3eef0c
Show file tree
Hide file tree
Showing 50 changed files with 1,660 additions and 170 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

### Features

* Added support for custom transactions in web client
* Added support for decimal values in the CLI (#454).
* Added serialization for `TransactionRequest` (#471).
* Added support for importing committed notes from older blocks than current (#472).
Expand Down
2 changes: 1 addition & 1 deletion crates/rust-client/src/rpc/tonic_client/generated/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct Note {
pub metadata: ::core::option::Option<NoteMetadata>,
#[prost(message, optional, tag = "5")]
pub merkle_path: ::core::option::Option<super::merkle::MerklePath>,
/// This field will be present when the note is on-chain.
/// This field will be present when the note is public.
/// details contain the `Note` in a serialized format.
#[prost(bytes = "vec", optional, tag = "6")]
pub details: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ pub struct ListNotesResponse {
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetAccountDetailsResponse {
/// Account info (with details for on-chain accounts)
/// Account info (with details for public accounts)
#[prost(message, optional, tag = "1")]
pub details: ::core::option::Option<super::account::AccountInfo>,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub struct Note {
pub metadata: ::core::option::Option<NoteMetadata>,
#[prost(message, optional, tag = "5")]
pub merkle_path: ::core::option::Option<super::merkle::MerklePath>,
/// This field will be present when the note is on-chain.
/// This field will be present when the note is public.
/// details contain the `Note` in a serialized format.
#[prost(bytes = "vec", optional, tag = "6")]
pub details: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ pub struct ListNotesResponse {
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GetAccountDetailsResponse {
/// Account info (with details for on-chain accounts)
/// Account info (with details for public accounts)
#[prost(message, optional, tag = "1")]
pub details: ::core::option::Option<super::account::AccountInfo>,
}
Expand Down
2 changes: 1 addition & 1 deletion crates/rust-client/src/store/web_store/accounts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ impl WebStore {
let js_value = JsFuture::from(promise).await.unwrap();
let account_code_idxdb: AccountCodeIdxdbObject = from_value(js_value).unwrap();

let code = AccountCode::from_bytes(&account_code_idxdb.account_code).unwrap();
let code = AccountCode::from_bytes(&account_code_idxdb.code).unwrap();

Ok(code)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/rust-client/src/store/web_store/accounts/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize};
pub struct AccountCodeIdxdbObject {
pub root: String,
#[serde(deserialize_with = "base64_to_vec_u8_required", default)]
pub account_code: Vec<u8>,
pub code: Vec<u8>,
}

#[derive(Serialize, Deserialize)]
Expand Down
21 changes: 6 additions & 15 deletions crates/rust-client/src/store/web_store/js/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,20 +170,14 @@ export async function getAccountCode(
// The first record is the only one due to the uniqueness constraint
const codeRecord = allMatchingRecords[0];

// Convert the module Blob to an ArrayBuffer
const moduleArrayBuffer = await codeRecord.module.arrayBuffer();
const moduleArray = new Uint8Array(moduleArrayBuffer);
const moduleBase64 = uint8ArrayToBase64(moduleArray);

// Convert the procedures Blob to an ArrayBuffer
const proceduresArrayBuffer = await codeRecord.procedures.arrayBuffer();
const proceduresArray = new Uint8Array(proceduresArrayBuffer);
const proceduresBase64 = uint8ArrayToBase64(proceduresArray);
// Convert the code Blob to an ArrayBuffer
const codeArrayBuffer = await codeRecord.code.arrayBuffer();
const codeArray = new Uint8Array(codeArrayBuffer);
const codeBase64 = uint8ArrayToBase64(codeArray);

return {
root: codeRecord.root,
procedures: proceduresBase64,
module: moduleBase64,
code: codeBase64,
};
} catch (error) {
console.error('Error fetching code record:', error);
Expand Down Expand Up @@ -350,18 +344,15 @@ export async function fetchAndCacheAccountAuthByPubKey(
export async function insertAccountCode(
codeRoot,
code,
module
) {
try {
// Create a Blob from the ArrayBuffer
const moduleBlob = new Blob([new Uint8Array(module)]);
const codeBlob = new Blob([new Uint8Array(code)]);

// Prepare the data object to insert
const data = {
root: codeRoot, // Using codeRoot as the key
procedures: codeBlob,
module: moduleBlob, // Blob created from ArrayBuffer
code: codeBlob,
};

// Perform the insert using Dexie
Expand Down
12 changes: 10 additions & 2 deletions crates/rust-client/src/store/web_store/js/notes.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,10 @@ export async function insertInputNote(
serializedNoteScript,
inclusionProof,
serializedCreatedAt,
expectedHeight,
ignored,
importedTag
importedTag,
nullifierHeight,
) {
return db.transaction('rw', inputNotes, notesScripts, async (tx) => {
try {
Expand All @@ -155,8 +157,10 @@ export async function insertInputNote(
inclusionProof: inclusionProof ? inclusionProof : null,
consumerTransactionId: null,
createdAt: serializedCreatedAt,
expectedHeight: expectedHeight ? expectedHeight : null,
ignored: ignored.toString(),
importedTag: importedTag ? importedTag : null
importedTag: importedTag ? importedTag : null,
nullifierHeight: nullifierHeight ? nullifierHeight : null
};

// Perform the insert using Dexie
Expand Down Expand Up @@ -188,6 +192,7 @@ export async function insertOutputNote(
serializedNoteScript,
inclusionProof,
serializedCreatedAt,
expectedHeight
) {
return db.transaction('rw', outputNotes, notesScripts, async (tx) => {
try {
Expand All @@ -204,6 +209,7 @@ export async function insertOutputNote(
inclusionProof: inclusionProof ? inclusionProof : null,
consumerTransactionId: null,
createdAt: serializedCreatedAt,
expectedHeight: expectedHeight ? expectedHeight : null, // todo change to block_num
ignored: "false",
imported_tag: null
};
Expand Down Expand Up @@ -336,6 +342,7 @@ async function processInputNotes(
serialized_note_script: serializedNoteScriptBase64,
consumer_account_id: consumerAccountId,
created_at: note.createdAt,
expected_height: note.expectedHeight ? note.expectedHeight : null,
submitted_at: note.submittedAt ? note.submittedAt : null,
nullifier_height: note.nullifierHeight ? note.nullifierHeight : null,
ignored: note.ignored === "true",
Expand Down Expand Up @@ -391,6 +398,7 @@ async function processOutputNotes(
serialized_note_script: serializedNoteScriptBase64,
consumer_account_id: consumerAccountId,
created_at: note.createdAt,
expected_height: note.expectedHeight ? note.expectedHeight : null,
submitted_at: note.submittedAt ? note.submittedAt : null,
nullifier_height: note.nullifierHeight ? note.nullifierHeight : null,
ignored: note.ignored === "true",
Expand Down
20 changes: 10 additions & 10 deletions crates/rust-client/src/store/web_store/js/transactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ export async function getTransactions(
// Create a map of scriptHash to script for quick lookup
const scriptMap = new Map();
scripts.forEach(script => {
scriptMap.set(script.scriptHash, script.program);
scriptMap.set(script.scriptHash, script.txScript);
});

const processedTransactions = await Promise.all(transactionRecords.map(async transactionRecord => {
let scriptBase64 = null;
let txScriptBase64 = null;

if (transactionRecord.scriptHash) {
const txScript = scriptMap.get(transactionRecord.scriptHash);

if (txScript) {
let scriptArrayBuffer = await txScript.arrayBuffer();
let scriptArray = new Uint8Array(scriptArrayBuffer);
scriptBase64 = uint8ArrayToBase64(scriptArray);
let txScriptArrayBuffer = await txScript.arrayBuffer();
let txScriptArray = new Uint8Array(txScriptArrayBuffer);
txScriptBase64 = uint8ArrayToBase64(txScriptArray);
}
}

Expand All @@ -58,7 +58,7 @@ export async function getTransactions(
input_notes: transactionRecord.inputNotes,
output_notes: transactionRecord.outputNotes,
script_hash: transactionRecord.scriptHash ? transactionRecord.scriptHash : null,
tx_script: scriptBase64,
tx_script: txScriptBase64,
block_num: transactionRecord.blockNum,
commit_height: transactionRecord.commitHeight ? transactionRecord.commitHeight : null
}
Expand Down Expand Up @@ -91,15 +91,15 @@ export async function insertTransactionScript(

let scriptHashArray = new Uint8Array(scriptHash);
let scriptHashBase64 = uint8ArrayToBase64(scriptHashArray);
let scriptBlob = null;

if (txScript ) {
scriptBlob = new Blob([new Uint8Array(txScript)]);
let txScriptBlob = null;
if (txScript) {
txScriptBlob = new Blob([new Uint8Array(txScript)]);
}

const data = {
scriptHash: scriptHashBase64,
script: scriptBlob
txScript: txScriptBlob
}

await transactionScripts.add(data);
Expand Down
3 changes: 3 additions & 0 deletions crates/rust-client/src/store/web_store/notes/js_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ extern "C" {
serialized_note_script: Vec<u8>,
inclusion_proof: Option<String>,
serialized_created_at: String,
expected_height: Option<String>,
ignored: bool,
imported_tag: Option<String>,
nullifier_height: Option<String>,
) -> js_sys::Promise;

#[wasm_bindgen(js_name = insertOutputNote)]
Expand All @@ -62,6 +64,7 @@ extern "C" {
serialized_note_script: Option<Vec<u8>>,
inclusion_proof: Option<String>,
serialized_created_at: String,
expected_height: Option<String>,
) -> js_sys::Promise;

#[wasm_bindgen(js_name = updateNoteConsumerTxId)]
Expand Down
4 changes: 3 additions & 1 deletion crates/rust-client/src/store/web_store/notes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ impl WebStore {
}

pub(crate) async fn insert_input_note(&self, note: InputNoteRecord) -> Result<(), StoreError> {
insert_input_note_tx(note).await
let block_num = self.get_sync_height().await?;

insert_input_note_tx(block_num, note).await
}

pub async fn update_note_inclusion_proof(
Expand Down
50 changes: 40 additions & 10 deletions crates/rust-client/src/store/web_store/notes/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ pub struct SerializedInputNoteData {
pub note_script: Vec<u8>,
pub inclusion_proof: Option<String>,
pub created_at: String,
pub expected_height: Option<String>,
pub ignored: bool,
pub imported_tag: Option<String>,
pub nullifier_height: Option<String>,
}

pub struct SerializedOutputNoteData {
Expand All @@ -51,6 +53,7 @@ pub struct SerializedOutputNoteData {
pub note_script: Option<Vec<u8>>,
pub inclusion_proof: Option<String>,
pub created_at: String,
pub expected_height: Option<String>,
}

// ================================================================================================
Expand Down Expand Up @@ -79,7 +82,7 @@ pub(crate) fn serialize_input_note(
let note_id = note.id().inner().to_string();
let note_assets = note.assets().to_bytes();

let (inclusion_proof, status) = match note.inclusion_proof() {
let inclusion_proof = match note.inclusion_proof() {
Some(proof) => {
let block_num = proof.location().block_num();
let node_index = proof.location().node_index_in_block();
Expand All @@ -91,14 +94,9 @@ pub(crate) fn serialize_input_note(
)?)
.map_err(StoreError::InputSerializationError)?;

let status = NOTE_STATUS_COMMITTED.to_string();
(Some(inclusion_proof), status)
},
None => {
let status = NOTE_STATUS_EXPECTED.to_string();

(None, status)
Some(inclusion_proof)
},
None => None,
};
let recipient = note.recipient().to_hex();

Expand All @@ -117,6 +115,23 @@ pub(crate) fn serialize_input_note(
let imported_tag: Option<u32> = note.imported_tag().map(|tag| tag.into());
let imported_tag_str: Option<String> = imported_tag.map(|tag| tag.to_string());

let (status, expected_height, nullifier_height) = match note.status() {
NoteStatus::Expected { block_height, .. } => {
let block_height_as_str = block_height.map(|height| height.to_string());
(NOTE_STATUS_EXPECTED.to_string(), block_height_as_str, None)
},
NoteStatus::Committed { .. } => (NOTE_STATUS_COMMITTED.to_string(), None, None),
NoteStatus::Processing { .. } => {
return Err(StoreError::DatabaseError(
"Processing notes should not be imported".to_string(),
))
},
NoteStatus::Consumed { block_height, .. } => {
let block_height_as_str = block_height.to_string();
(NOTE_STATUS_CONSUMED.to_string(), None, Some(block_height_as_str))
},
};

Ok(SerializedInputNoteData {
note_id,
note_assets,
Expand All @@ -128,13 +143,16 @@ pub(crate) fn serialize_input_note(
note_script,
inclusion_proof,
created_at,
expected_height,
ignored,
imported_tag: imported_tag_str,
nullifier_height,
})
}

pub async fn insert_input_note_tx(note: InputNoteRecord) -> Result<(), StoreError> {
pub async fn insert_input_note_tx(block_num: u32, note: InputNoteRecord) -> Result<(), StoreError> {
let serialized_data = serialize_input_note(note)?;
let expected_height = serialized_data.expected_height.or(Some(block_num.to_string()));

let promise = idxdb_insert_input_note(
serialized_data.note_id,
Expand All @@ -147,8 +165,10 @@ pub async fn insert_input_note_tx(note: InputNoteRecord) -> Result<(), StoreErro
serialized_data.note_script,
serialized_data.inclusion_proof,
serialized_data.created_at,
expected_height,
serialized_data.ignored,
serialized_data.imported_tag,
serialized_data.nullifier_height,
);
JsFuture::from(promise).await.unwrap();

Expand Down Expand Up @@ -195,6 +215,10 @@ pub(crate) fn serialize_output_note(
let note_script_hash = note.details().map(|details| details.script_hash().to_hex());
let note_script = note.details().map(|details| details.script().to_bytes());
let created_at = Utc::now().timestamp().to_string();
let expected_height = match note.status() {
NoteStatus::Expected { block_height, .. } => block_height.map(|height| height.to_string()),
_ => None,
};

Ok(SerializedOutputNoteData {
note_id,
Expand All @@ -207,11 +231,16 @@ pub(crate) fn serialize_output_note(
note_script,
inclusion_proof,
created_at,
expected_height,
})
}

pub async fn insert_output_note_tx(note: &OutputNoteRecord) -> Result<(), StoreError> {
pub async fn insert_output_note_tx(
block_num: u32,
note: &OutputNoteRecord,
) -> Result<(), StoreError> {
let serialized_data = serialize_output_note(note)?;
let expected_height = serialized_data.expected_height.or(Some(block_num.to_string()));

let result = JsFuture::from(idxdb_insert_output_note(
serialized_data.note_id,
Expand All @@ -224,6 +253,7 @@ pub async fn insert_output_note_tx(note: &OutputNoteRecord) -> Result<(), StoreE
serialized_data.note_script,
serialized_data.inclusion_proof,
serialized_data.created_at,
expected_height,
))
.await;
match result {
Expand Down
7 changes: 6 additions & 1 deletion crates/rust-client/src/store/web_store/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,12 @@ impl WebStore {
// TODO: LOP INTO idxdb_apply_state_sync call
// Commit new public notes
for note in committed_notes.new_public_notes() {
insert_input_note_tx(note.clone().into()).await.unwrap();
insert_input_note_tx(
note.location().expect("new public note should be authenticated").block_num(),
note.clone().into(),
)
.await
.unwrap();
}

// Serialize data for updating committed transactions
Expand Down
Loading

0 comments on commit f3eef0c

Please sign in to comment.