Merge branch 'main' into schouhy/add-pinata-program

This commit is contained in:
Sergio Chouhy 2025-09-18 16:44:31 -03:00
commit cca835e016
48 changed files with 1471 additions and 830 deletions

View File

@ -41,6 +41,7 @@ ark-bn254 = "0.5.0"
ark-ff = "0.5.0" ark-ff = "0.5.0"
tiny-keccak = { version = "2.0.2", features = ["keccak"] } tiny-keccak = { version = "2.0.2", features = ["keccak"] }
base64 = "0.22.1" base64 = "0.22.1"
chrono = "0.4.41"
rocksdb = { version = "0.21.0", default-features = false, features = [ rocksdb = { version = "0.21.0", default-features = false, features = [
"snappy", "snappy",

View File

@ -1,41 +1,56 @@
use rs_merkle::Hasher;
use std::io::{Cursor, Read}; use std::io::{Cursor, Read};
use rs_merkle::Hasher; use crate::{OwnHasher, transaction::EncodedTransaction};
use crate::merkle_tree_public::hasher::OwnHasher;
use nssa;
pub type BlockHash = [u8; 32]; pub type BlockHash = [u8; 32];
pub type BlockId = u64; pub type BlockId = u64;
pub type TimeStamp = u64;
#[derive(Debug, Clone)]
pub struct BlockHeader {
pub block_id: BlockId,
pub prev_block_hash: BlockHash,
pub hash: BlockHash,
pub timestamp: TimeStamp,
pub signature: nssa::Signature,
}
#[derive(Debug, Clone)]
pub struct BlockBody {
pub transactions: Vec<EncodedTransaction>,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Block { pub struct Block {
pub block_id: BlockId, pub header: BlockHeader,
pub prev_block_id: BlockId, pub body: BlockBody,
pub prev_block_hash: BlockHash,
pub hash: BlockHash,
pub transactions: Vec<nssa::PublicTransaction>,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct HashableBlockData { pub struct HashableBlockData {
pub block_id: BlockId, pub block_id: BlockId,
pub prev_block_id: BlockId,
pub prev_block_hash: BlockHash, pub prev_block_hash: BlockHash,
pub transactions: Vec<nssa::PublicTransaction>, pub timestamp: TimeStamp,
pub transactions: Vec<EncodedTransaction>,
} }
impl From<HashableBlockData> for Block { impl HashableBlockData {
fn from(value: HashableBlockData) -> Self { pub fn into_block(self, signing_key: &nssa::PrivateKey) -> Block {
let data = value.to_bytes(); let data_bytes = self.to_bytes();
let hash = OwnHasher::hash(&data); let signature = nssa::Signature::new(signing_key, &data_bytes);
let hash = OwnHasher::hash(&data_bytes);
Self { Block {
block_id: value.block_id, header: BlockHeader {
prev_block_id: value.prev_block_id, block_id: self.block_id,
hash, prev_block_hash: self.prev_block_hash,
transactions: value.transactions, hash,
prev_block_hash: value.prev_block_hash, timestamp: self.timestamp,
signature,
},
body: BlockBody {
transactions: self.transactions,
},
} }
} }
} }
@ -43,10 +58,10 @@ impl From<HashableBlockData> for Block {
impl From<Block> for HashableBlockData { impl From<Block> for HashableBlockData {
fn from(value: Block) -> Self { fn from(value: Block) -> Self {
Self { Self {
block_id: value.block_id, block_id: value.header.block_id,
prev_block_id: value.prev_block_id, prev_block_hash: value.header.prev_block_hash,
prev_block_hash: value.prev_block_hash, timestamp: value.header.timestamp,
transactions: value.transactions, transactions: value.body.transactions,
} }
} }
} }
@ -55,11 +70,15 @@ impl HashableBlockData {
pub fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new(); let mut bytes = Vec::new();
bytes.extend_from_slice(&self.block_id.to_le_bytes()); bytes.extend_from_slice(&self.block_id.to_le_bytes());
bytes.extend_from_slice(&self.prev_block_id.to_le_bytes());
bytes.extend_from_slice(&self.prev_block_hash); bytes.extend_from_slice(&self.prev_block_hash);
bytes.extend_from_slice(&self.timestamp.to_le_bytes());
let num_transactions: u32 = self.transactions.len() as u32; let num_transactions: u32 = self.transactions.len() as u32;
bytes.extend_from_slice(&num_transactions.to_le_bytes()); bytes.extend_from_slice(&num_transactions.to_le_bytes());
for tx in &self.transactions { for tx in &self.transactions {
let transaction_bytes = tx.to_bytes();
let num_transaction_bytes: u32 = transaction_bytes.len() as u32;
bytes.extend_from_slice(&num_transaction_bytes.to_le_bytes());
bytes.extend_from_slice(&tx.to_bytes()); bytes.extend_from_slice(&tx.to_bytes());
} }
bytes bytes
@ -70,37 +89,47 @@ impl HashableBlockData {
let mut cursor = Cursor::new(data); let mut cursor = Cursor::new(data);
let block_id = u64_from_cursor(&mut cursor); let block_id = u64_from_cursor(&mut cursor);
let prev_block_id = u64_from_cursor(&mut cursor);
let mut prev_block_hash = [0u8; 32]; let mut prev_block_hash = [0u8; 32];
cursor.read_exact(&mut prev_block_hash).unwrap(); cursor.read_exact(&mut prev_block_hash).unwrap();
let timestamp = u64_from_cursor(&mut cursor);
let num_transactions = u32_from_cursor(&mut cursor) as usize; let num_transactions = u32_from_cursor(&mut cursor) as usize;
let mut transactions = Vec::with_capacity(num_transactions); let mut transactions = Vec::with_capacity(num_transactions);
for _ in 0..num_transactions { for _ in 0..num_transactions {
let tx = nssa::PublicTransaction::from_cursor(&mut cursor).unwrap(); let tx_len = u32_from_cursor(&mut cursor) as usize;
let mut tx_bytes = Vec::with_capacity(tx_len);
for _ in 0..tx_len {
let mut buff = [0; 1];
cursor.read_exact(&mut buff).unwrap();
tx_bytes.push(buff[0]);
}
let tx = EncodedTransaction::from_bytes(tx_bytes);
transactions.push(tx); transactions.push(tx);
} }
Self { Self {
block_id, block_id,
prev_block_id,
prev_block_hash, prev_block_hash,
timestamp,
transactions, transactions,
} }
} }
} }
// TODO: Improve error handling. Remove unwraps. // TODO: Improve error handling. Remove unwraps.
fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 { pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 {
let mut word_buf = [0u8; 4]; let mut word_buf = [0u8; 4];
cursor.read_exact(&mut word_buf).unwrap(); cursor.read_exact(&mut word_buf).unwrap();
u32::from_le_bytes(word_buf) u32::from_le_bytes(word_buf)
} }
// TODO: Improve error handling. Remove unwraps. // TODO: Improve error handling. Remove unwraps.
fn u64_from_cursor(cursor: &mut Cursor<&[u8]>) -> u64 { pub fn u64_from_cursor(cursor: &mut Cursor<&[u8]>) -> u64 {
let mut word_buf = [0u8; 8]; let mut word_buf = [0u8; 8];
cursor.read_exact(&mut word_buf).unwrap(); cursor.read_exact(&mut word_buf).unwrap();
u64::from_le_bytes(word_buf) u64::from_le_bytes(word_buf)

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::merkle_tree_public::CommitmentHashType; use crate::CommitmentHashType;
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
pub struct Commitment { pub struct Commitment {

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::merkle_tree_public::TreeHashType; use crate::TreeHashType;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublicNativeTokenSend { pub struct PublicNativeTokenSend {

View File

@ -1,10 +1,10 @@
use merkle_tree_public::TreeHashType; use rs_merkle::Hasher;
use serde::Deserialize; use serde::Deserialize;
use sha2::{Digest, Sha256, digest::FixedOutput};
pub mod block; pub mod block;
pub mod commitment; pub mod commitment;
pub mod execution_input; pub mod execution_input;
pub mod merkle_tree_public;
pub mod nullifier; pub mod nullifier;
pub mod rpc_primitives; pub mod rpc_primitives;
pub mod sequencer_client; pub mod sequencer_client;
@ -16,6 +16,25 @@ pub mod test_utils;
use rpc_primitives::errors::RpcError; use rpc_primitives::errors::RpcError;
pub type TreeHashType = [u8; 32];
pub type CommitmentHashType = Vec<u8>;
#[derive(Debug, Clone)]
///Our own hasher.
/// Currently it is SHA256 hasher wrapper. May change in a future.
pub struct OwnHasher {}
impl Hasher for OwnHasher {
type Hash = TreeHashType;
fn hash(data: &[u8]) -> TreeHashType {
let mut hasher = Sha256::new();
hasher.update(data);
<TreeHashType>::from(hasher.finalize_fixed())
}
}
///Account id on blockchain ///Account id on blockchain
pub type AccountId = TreeHashType; pub type AccountId = TreeHashType;

View File

@ -1,20 +0,0 @@
use rs_merkle::Hasher;
use sha2::{Digest, Sha256, digest::FixedOutput};
use super::TreeHashType;
#[derive(Debug, Clone)]
///Our own hasher.
/// Currently it is SHA256 hasher wrapper. May change in a future.
pub struct OwnHasher {}
impl Hasher for OwnHasher {
type Hash = TreeHashType;
fn hash(data: &[u8]) -> TreeHashType {
let mut hasher = Sha256::new();
hasher.update(data);
<TreeHashType>::from(hasher.finalize_fixed())
}
}

View File

@ -1,312 +0,0 @@
use std::{collections::HashMap, fmt, marker::PhantomData};
use rs_merkle::{MerkleProof, MerkleTree};
use serde::{
Deserialize, Deserializer, Serialize,
de::{SeqAccess, Visitor},
ser::SerializeSeq,
};
use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment};
use super::{TreeHashType, hasher::OwnHasher, tree_leav_item::TreeLeavItem};
#[derive(Clone)]
pub struct HashStorageMerkleTree<Leav: TreeLeavItem + Clone> {
leaves: HashMap<usize, Leav>,
hash_to_id_map: HashMap<TreeHashType, usize>,
tree: MerkleTree<OwnHasher>,
}
impl<Leav: TreeLeavItem + Clone + Serialize> Serialize for HashStorageMerkleTree<Leav> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut vector = self.leaves.iter().collect::<Vec<_>>();
vector.sort_by(|a, b| a.0.cmp(b.0));
let mut seq = serializer.serialize_seq(Some(self.leaves.len()))?;
for element in vector.iter() {
seq.serialize_element(element.1)?;
}
seq.end()
}
}
struct HashStorageMerkleTreeDeserializer<Leav: TreeLeavItem + Clone> {
marker: PhantomData<fn() -> HashStorageMerkleTree<Leav>>,
}
impl<Leaf: TreeLeavItem + Clone> HashStorageMerkleTreeDeserializer<Leaf> {
fn new() -> Self {
HashStorageMerkleTreeDeserializer {
marker: PhantomData,
}
}
}
impl<'de, Leav: TreeLeavItem + Clone + Deserialize<'de>> Visitor<'de>
for HashStorageMerkleTreeDeserializer<Leav>
{
type Value = HashStorageMerkleTree<Leav>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("HashStorageMerkleTree key value sequence.")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut vector = vec![];
loop {
let opt_key = seq.next_element::<Leav>()?;
if let Some(value) = opt_key {
vector.push(value);
} else {
break;
}
}
Ok(HashStorageMerkleTree::new(vector))
}
}
impl<'de, Leav: TreeLeavItem + Clone + Deserialize<'de>> serde::Deserialize<'de>
for HashStorageMerkleTree<Leav>
{
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_seq(HashStorageMerkleTreeDeserializer::new())
}
}
pub type PublicTransactionMerkleTree = HashStorageMerkleTree<Transaction>;
pub type UTXOCommitmentsMerkleTree = HashStorageMerkleTree<UTXOCommitment>;
impl<Leav: TreeLeavItem + Clone> HashStorageMerkleTree<Leav> {
pub fn new(leaves_vec: Vec<Leav>) -> Self {
let mut leaves_map = HashMap::new();
let mut hash_to_id_map = HashMap::new();
let leaves_hashed: Vec<TreeHashType> = leaves_vec
.iter()
.enumerate()
.map(|(id, tx)| {
leaves_map.insert(id, tx.clone());
hash_to_id_map.insert(tx.hash(), id);
tx.hash()
})
.collect();
Self {
leaves: leaves_map,
hash_to_id_map,
tree: MerkleTree::from_leaves(&leaves_hashed),
}
}
pub fn get_tx(&self, hash: TreeHashType) -> Option<&Leav> {
self.hash_to_id_map
.get(&hash)
.and_then(|id| self.leaves.get(id))
}
pub fn get_root(&self) -> Option<TreeHashType> {
self.tree.root()
}
pub fn get_proof(&self, hash: TreeHashType) -> Option<MerkleProof<OwnHasher>> {
self.hash_to_id_map
.get(&hash)
.map(|id| self.tree.proof(&[*id]))
}
pub fn get_proof_multiple(&self, hashes: &[TreeHashType]) -> Option<MerkleProof<OwnHasher>> {
let ids_opt: Vec<Option<&usize>> = hashes
.iter()
.map(|hash| self.hash_to_id_map.get(hash))
.collect();
let is_valid = ids_opt.iter().all(|el| el.is_some());
if is_valid {
let ids: Vec<usize> = ids_opt.into_iter().map(|el| *el.unwrap()).collect();
Some(self.tree.proof(&ids))
} else {
None
}
}
pub fn add_tx(&mut self, tx: &Leav) {
let last = self.leaves.len();
self.leaves.insert(last, tx.clone());
self.hash_to_id_map.insert(tx.hash(), last);
self.tree.insert(tx.hash());
self.tree.commit();
}
pub fn add_tx_multiple(&mut self, txs: Vec<Leav>) {
for tx in txs.iter() {
let last = self.leaves.len();
self.leaves.insert(last, tx.clone());
self.hash_to_id_map.insert(tx.hash(), last);
}
self.tree
.append(&mut txs.iter().map(|tx| tx.hash()).collect());
self.tree.commit();
}
}
#[cfg(test)]
mod tests {
use super::*;
// Mock implementation of TreeLeavItem trait for testing
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
struct MockTransaction {
pub hash: TreeHashType,
}
impl TreeLeavItem for MockTransaction {
fn hash(&self) -> TreeHashType {
self.hash
}
}
fn get_first_32_bytes(s: &str) -> [u8; 32] {
let mut buffer = [0u8; 32];
let bytes = s.as_bytes();
let len = std::cmp::min(32, bytes.len());
buffer[..len].copy_from_slice(&bytes[..len]);
buffer
}
#[test]
fn test_new_merkle_tree() {
let tx1 = MockTransaction {
hash: get_first_32_bytes("tx1"),
};
let tx2 = MockTransaction {
hash: get_first_32_bytes("tx2"),
};
let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]);
assert_eq!(tree.leaves.len(), 2);
assert!(tree.get_root().is_some());
}
#[test]
fn test_new_merkle_tree_serialize() {
let tx1 = MockTransaction {
hash: get_first_32_bytes("tx1"),
};
let tx2 = MockTransaction {
hash: get_first_32_bytes("tx2"),
};
let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]);
let binding = serde_json::to_vec(&tree).unwrap();
let obj: HashStorageMerkleTree<MockTransaction> = serde_json::from_slice(&binding).unwrap();
assert_eq!(tree.leaves, obj.leaves);
assert_eq!(tree.hash_to_id_map, obj.hash_to_id_map);
assert_eq!(tree.tree.root(), obj.tree.root());
}
#[test]
fn test_get_tx() {
let tx1 = MockTransaction {
hash: get_first_32_bytes("tx1"),
};
let tx2 = MockTransaction {
hash: get_first_32_bytes("tx2"),
};
let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]);
assert_eq!(tree.get_tx(tx1.hash()), Some(&tx1));
assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2));
}
#[test]
fn test_get_proof() {
let tx1 = MockTransaction {
hash: get_first_32_bytes("tx1"),
};
let tx2 = MockTransaction {
hash: get_first_32_bytes("tx2"),
};
let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone()]);
let proof = tree.get_proof(tx1.hash());
assert!(proof.is_some());
}
#[test]
fn test_add_tx() {
let tx1 = MockTransaction {
hash: get_first_32_bytes("tx1"),
};
let tx2 = MockTransaction {
hash: get_first_32_bytes("tx2"),
};
let mut tree = HashStorageMerkleTree::new(vec![tx1.clone()]);
tree.add_tx(&tx2);
assert_eq!(tree.leaves.len(), 2);
assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2));
}
#[test]
fn test_add_tx_multiple() {
let tx1 = MockTransaction {
hash: get_first_32_bytes("tx1"),
};
let tx2 = MockTransaction {
hash: get_first_32_bytes("tx2"),
};
let tx3 = MockTransaction {
hash: get_first_32_bytes("tx3"),
};
let mut tree = HashStorageMerkleTree::new(vec![tx1.clone()]);
tree.add_tx_multiple(vec![tx2.clone(), tx3.clone()]);
assert_eq!(tree.leaves.len(), 3);
assert_eq!(tree.get_tx(tx2.hash()), Some(&tx2));
assert_eq!(tree.get_tx(tx3.hash()), Some(&tx3));
}
#[test]
fn test_get_proof_multiple() {
let tx1 = MockTransaction {
hash: get_first_32_bytes("tx1"),
};
let tx2 = MockTransaction {
hash: get_first_32_bytes("tx2"),
};
let tx3 = MockTransaction {
hash: get_first_32_bytes("tx3"),
};
let tree = HashStorageMerkleTree::new(vec![tx1.clone(), tx2.clone(), tx3.clone()]);
let proof = tree.get_proof_multiple(&[tx1.hash(), tx2.hash()]);
assert!(proof.is_some());
}
}

View File

@ -1,6 +0,0 @@
pub mod hasher;
pub mod merkle_tree;
pub mod tree_leav_item;
pub type TreeHashType = [u8; 32];
pub type CommitmentHashType = Vec<u8>;

View File

@ -1,19 +0,0 @@
use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment};
use super::TreeHashType;
pub trait TreeLeavItem {
fn hash(&self) -> TreeHashType;
}
impl TreeLeavItem for Transaction {
fn hash(&self) -> TreeHashType {
self.body().hash()
}
}
impl TreeLeavItem for UTXOCommitment {
fn hash(&self) -> TreeHashType {
self.hash
}
}

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::merkle_tree_public::TreeHashType; use crate::TreeHashType;
//ToDo: Update Nullifier model, when it is clear //ToDo: Update Nullifier model, when it is clear
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]

View File

@ -49,7 +49,7 @@ pub struct GetAccountsNoncesRequest {
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GetAccountDataRequest { pub struct GetAccountRequest {
pub address: String, pub address: String,
} }
@ -63,7 +63,7 @@ parse_request!(GetInitialTestnetAccountsRequest);
parse_request!(GetAccountBalanceRequest); parse_request!(GetAccountBalanceRequest);
parse_request!(GetTransactionByHashRequest); parse_request!(GetTransactionByHashRequest);
parse_request!(GetAccountsNoncesRequest); parse_request!(GetAccountsNoncesRequest);
parse_request!(GetAccountDataRequest); parse_request!(GetAccountRequest);
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct HelloResponse { pub struct HelloResponse {
@ -112,9 +112,6 @@ pub struct GetTransactionByHashResponse {
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GetAccountDataResponse { pub struct GetAccountResponse {
pub balance: u128, pub account: nssa::Account,
pub nonce: u128,
pub program_owner: [u32; 8],
pub data: Vec<u8>,
} }

View File

@ -8,10 +8,11 @@ use reqwest::Client;
use serde_json::Value; use serde_json::Value;
use crate::rpc_primitives::requests::{ use crate::rpc_primitives::requests::{
GetAccountsNoncesRequest, GetAccountsNoncesResponse, GetTransactionByHashRequest, GetAccountRequest, GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse,
GetTransactionByHashResponse, GetTransactionByHashRequest, GetTransactionByHashResponse,
}; };
use crate::sequencer_client::json::AccountInitialData; use crate::sequencer_client::json::AccountInitialData;
use crate::transaction::{EncodedTransaction, NSSATransaction};
use crate::{SequencerClientError, SequencerRpcError}; use crate::{SequencerClientError, SequencerRpcError};
pub mod json; pub mod json;
@ -108,6 +109,21 @@ impl SequencerClient {
Ok(resp_deser) Ok(resp_deser)
} }
pub async fn get_account(
&self,
address: String,
) -> Result<GetAccountResponse, SequencerClientError> {
let block_req = GetAccountRequest { address };
let req = serde_json::to_value(block_req)?;
let resp = self.call_method_with_payload("get_account", req).await?;
let resp_deser = serde_json::from_value(resp)?;
Ok(resp_deser)
}
///Get transaction details for `hash`. ///Get transaction details for `hash`.
pub async fn get_transaction_by_hash( pub async fn get_transaction_by_hash(
&self, &self,
@ -127,10 +143,12 @@ impl SequencerClient {
} }
///Send transaction to sequencer ///Send transaction to sequencer
pub async fn send_tx( pub async fn send_tx_public(
&self, &self,
transaction: nssa::PublicTransaction, transaction: nssa::PublicTransaction,
) -> Result<SendTxResponse, SequencerClientError> { ) -> Result<SendTxResponse, SequencerClientError> {
let transaction = EncodedTransaction::from(NSSATransaction::Public(transaction));
let tx_req = SendTxRequest { let tx_req = SendTxRequest {
transaction: transaction.to_bytes(), transaction: transaction.to_bytes(),
}; };

View File

@ -1,6 +1,13 @@
use nssa; use crate::{
block::{Block, HashableBlockData},
transaction::{EncodedTransaction, NSSATransaction},
};
use crate::block::{Block, HashableBlockData}; //Helpers
pub fn sequencer_sign_key_for_testing() -> nssa::PrivateKey {
nssa::PrivateKey::try_new([37; 32]).unwrap()
}
//Dummy producers //Dummy producers
@ -10,23 +17,23 @@ use crate::block::{Block, HashableBlockData};
/// ///
/// `prev_hash` - hash of previous block, provide None for genesis /// `prev_hash` - hash of previous block, provide None for genesis
/// ///
/// `transactions` - vector of `Transaction` objects /// `transactions` - vector of `EncodedTransaction` objects
pub fn produce_dummy_block( pub fn produce_dummy_block(
id: u64, id: u64,
prev_hash: Option<[u8; 32]>, prev_hash: Option<[u8; 32]>,
transactions: Vec<nssa::PublicTransaction>, transactions: Vec<EncodedTransaction>,
) -> Block { ) -> Block {
let block_data = HashableBlockData { let block_data = HashableBlockData {
block_id: id, block_id: id,
prev_block_id: id.saturating_sub(1),
prev_block_hash: prev_hash.unwrap_or_default(), prev_block_hash: prev_hash.unwrap_or_default(),
timestamp: id * 100,
transactions, transactions,
}; };
block_data.into() block_data.into_block(&sequencer_sign_key_for_testing())
} }
pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction { pub fn produce_dummy_empty_transaction() -> EncodedTransaction {
let program_id = nssa::program::Program::authenticated_transfer_program().id(); let program_id = nssa::program::Program::authenticated_transfer_program().id();
let addresses = vec![]; let addresses = vec![];
let nonces = vec![]; let nonces = vec![];
@ -36,7 +43,10 @@ pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction {
.unwrap(); .unwrap();
let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap(); let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&private_key]); let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&private_key]);
nssa::PublicTransaction::new(message, witness_set)
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
EncodedTransaction::from(NSSATransaction::Public(nssa_tx))
} }
pub fn create_transaction_native_token_transfer( pub fn create_transaction_native_token_transfer(
@ -45,7 +55,7 @@ pub fn create_transaction_native_token_transfer(
to: [u8; 32], to: [u8; 32],
balance_to_move: u128, balance_to_move: u128,
signing_key: nssa::PrivateKey, signing_key: nssa::PrivateKey,
) -> nssa::PublicTransaction { ) -> EncodedTransaction {
let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)]; let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)];
let nonces = vec![nonce]; let nonces = vec![nonce];
let program_id = nssa::program::Program::authenticated_transfer_program().id(); let program_id = nssa::program::Program::authenticated_transfer_program().id();
@ -53,5 +63,8 @@ pub fn create_transaction_native_token_transfer(
nssa::public_transaction::Message::try_new(program_id, addresses, nonces, balance_to_move) nssa::public_transaction::Message::try_new(program_id, addresses, nonces, balance_to_move)
.unwrap(); .unwrap();
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]); let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[&signing_key]);
nssa::PublicTransaction::new(message, witness_set)
let nssa_tx = nssa::PublicTransaction::new(message, witness_set);
EncodedTransaction::from(NSSATransaction::Public(nssa_tx))
} }

View File

@ -1,22 +1,34 @@
use k256::ecdsa::{ use k256::ecdsa::{Signature, SigningKey, VerifyingKey};
Signature, SigningKey, VerifyingKey,
signature::{Signer, Verifier},
};
use log::info; use log::info;
use secp256k1_zkp::{PedersenCommitment, Tweak};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, digest::FixedOutput}; use sha2::{Digest, digest::FixedOutput};
use crate::merkle_tree_public::TreeHashType;
use elliptic_curve::{ use elliptic_curve::{
consts::{B0, B1}, consts::{B0, B1},
generic_array::GenericArray, generic_array::GenericArray,
}; };
use sha2::digest::typenum::{UInt, UTerm}; use sha2::digest::typenum::{UInt, UTerm};
use crate::TransactionSignatureError; #[derive(Debug, Clone, PartialEq, Eq)]
pub enum NSSATransaction {
Public(nssa::PublicTransaction),
PrivacyPreserving(nssa::PrivacyPreservingTransaction),
}
impl From<nssa::PublicTransaction> for NSSATransaction {
fn from(value: nssa::PublicTransaction) -> Self {
Self::Public(value)
}
}
impl From<nssa::PrivacyPreservingTransaction> for NSSATransaction {
fn from(value: nssa::PrivacyPreservingTransaction) -> Self {
Self::PrivacyPreserving(value)
}
}
use crate::TreeHashType;
pub type CipherText = Vec<u8>; pub type CipherText = Vec<u8>;
pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>; pub type Nonce = GenericArray<u8, UInt<UInt<UInt<UInt<UTerm, B1>, B1>, B0>, B0>>;
@ -25,39 +37,45 @@ pub type Tag = u8;
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum TxKind { pub enum TxKind {
Public, Public,
Private, PrivacyPreserving,
Shielded,
Deshielded,
} }
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
///General transaction object ///General transaction object
pub struct TransactionBody { pub struct EncodedTransaction {
pub tx_kind: TxKind, pub tx_kind: TxKind,
///Tx input data (public part)
pub execution_input: Vec<u8>,
///Tx output data (public_part)
pub execution_output: Vec<u8>,
///Tx input utxo commitments
pub utxo_commitments_spent_hashes: Vec<TreeHashType>,
///Tx output utxo commitments
pub utxo_commitments_created_hashes: Vec<TreeHashType>,
///Tx output nullifiers
pub nullifier_created_hashes: Vec<TreeHashType>,
///Execution proof (private part)
pub execution_proof_private: String,
///Encoded blobs of data ///Encoded blobs of data
pub encoded_data: Vec<(CipherText, Vec<u8>, Tag)>, pub encoded_transaction_data: Vec<u8>,
///Transaction senders ephemeral pub key }
pub ephemeral_pub_key: Vec<u8>,
///Public (Pedersen) commitment impl From<NSSATransaction> for EncodedTransaction {
pub commitment: Vec<PedersenCommitment>, fn from(value: NSSATransaction) -> Self {
///tweak match value {
pub tweak: Tweak, NSSATransaction::Public(tx) => Self {
///secret_r tx_kind: TxKind::Public,
pub secret_r: [u8; 32], encoded_transaction_data: tx.to_bytes(),
///Hex-encoded address of a smart contract account called },
pub sc_addr: String, NSSATransaction::PrivacyPreserving(tx) => Self {
tx_kind: TxKind::PrivacyPreserving,
encoded_transaction_data: tx.to_bytes(),
},
}
}
}
impl TryFrom<&EncodedTransaction> for NSSATransaction {
type Error = nssa::error::NssaError;
fn try_from(value: &EncodedTransaction) -> Result<Self, Self::Error> {
match value.tx_kind {
TxKind::Public => nssa::PublicTransaction::from_bytes(&value.encoded_transaction_data)
.map(|tx| tx.into()),
TxKind::PrivacyPreserving => {
nssa::PrivacyPreservingTransaction::from_bytes(&value.encoded_transaction_data)
.map(|tx| tx.into())
}
}
}
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -153,7 +171,7 @@ impl ActionData {
} }
} }
impl TransactionBody { impl EncodedTransaction {
/// Computes and returns the SHA-256 hash of the JSON-serialized representation of `self`. /// Computes and returns the SHA-256 hash of the JSON-serialized representation of `self`.
pub fn hash(&self) -> TreeHashType { pub fn hash(&self) -> TreeHashType {
let bytes_to_hash = self.to_bytes(); let bytes_to_hash = self.to_bytes();
@ -162,180 +180,48 @@ impl TransactionBody {
TreeHashType::from(hasher.finalize_fixed()) TreeHashType::from(hasher.finalize_fixed())
} }
fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
// TODO: Remove `unwrap` by implementing a `to_bytes` method // TODO: Remove `unwrap` by implementing a `to_bytes` method
// that deterministically encodes all transaction fields to bytes // that deterministically encodes all transaction fields to bytes
// and guarantees serialization will succeed. // and guarantees serialization will succeed.
serde_json::to_vec(&self).unwrap() serde_json::to_vec(&self).unwrap()
} }
pub fn from_bytes(bytes: Vec<u8>) -> Self {
serde_json::from_slice(&bytes).unwrap()
}
pub fn log(&self) { pub fn log(&self) {
info!("Transaction hash is {:?}", hex::encode(self.hash())); info!("Transaction hash is {:?}", hex::encode(self.hash()));
info!("Transaction tx_kind is {:?}", self.tx_kind); info!("Transaction tx_kind is {:?}", self.tx_kind);
info!("Transaction execution_input is {:?}", {
if let Ok(action) = serde_json::from_slice::<ActionData>(&self.execution_input) {
action.into_hexed_print()
} else {
"".to_string()
}
});
info!("Transaction execution_output is {:?}", {
if let Ok(action) = serde_json::from_slice::<ActionData>(&self.execution_output) {
action.into_hexed_print()
} else {
"".to_string()
}
});
info!(
"Transaction utxo_commitments_spent_hashes is {:?}",
self.utxo_commitments_spent_hashes
.iter()
.map(|val| hex::encode(*val))
.collect::<Vec<_>>()
);
info!(
"Transaction utxo_commitments_created_hashes is {:?}",
self.utxo_commitments_created_hashes
.iter()
.map(|val| hex::encode(*val))
.collect::<Vec<_>>()
);
info!(
"Transaction nullifier_created_hashes is {:?}",
self.nullifier_created_hashes
.iter()
.map(|val| hex::encode(*val))
.collect::<Vec<_>>()
);
info!(
"Transaction encoded_data is {:?}",
self.encoded_data
.iter()
.map(|val| (hex::encode(val.0.clone()), hex::encode(val.1.clone())))
.collect::<Vec<_>>()
);
info!(
"Transaction ephemeral_pub_key is {:?}",
hex::encode(self.ephemeral_pub_key.clone())
);
} }
} }
type TransactionHash = [u8; 32];
pub type TransactionSignature = Signature; pub type TransactionSignature = Signature;
pub type SignaturePublicKey = VerifyingKey; pub type SignaturePublicKey = VerifyingKey;
pub type SignaturePrivateKey = SigningKey; pub type SignaturePrivateKey = SigningKey;
/// A container for a transaction body with a signature.
/// Meant to be sent through the network to the sequencer
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Transaction {
body: TransactionBody,
pub signature: TransactionSignature,
pub public_key: VerifyingKey,
}
impl Transaction {
/// Returns a new transaction signed with the provided `private_key`.
/// The signature is generated over the hash of the body as computed by `body.hash()`
pub fn new(body: TransactionBody, private_key: SigningKey) -> Transaction {
let signature: TransactionSignature = private_key.sign(&body.to_bytes());
let public_key = VerifyingKey::from(&private_key);
Self {
body,
signature,
public_key,
}
}
/// Converts the transaction into an `AuthenticatedTransaction` by verifying its signature.
/// Returns an error if the signature verification fails.
pub fn into_authenticated(self) -> Result<AuthenticatedTransaction, TransactionSignatureError> {
let hash = self.body.hash();
self.public_key
.verify(&self.body.to_bytes(), &self.signature)
.map_err(|_| TransactionSignatureError::InvalidSignature)?;
Ok(AuthenticatedTransaction {
hash,
transaction: self,
})
}
/// Returns the body of the transaction
pub fn body(&self) -> &TransactionBody {
&self.body
}
}
/// A transaction with a valid signature over the hash of its body.
/// Can only be constructed from an `Transaction`
/// if the signature is valid
#[derive(Debug, Clone)]
pub struct AuthenticatedTransaction {
hash: TransactionHash,
transaction: Transaction,
}
impl AuthenticatedTransaction {
/// Returns the underlying transaction
pub fn transaction(&self) -> &Transaction {
&self.transaction
}
pub fn into_transaction(self) -> Transaction {
self.transaction
}
/// Returns the precomputed hash over the body of the transaction
pub fn hash(&self) -> &TransactionHash {
&self.hash
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use k256::{FieldBytes, ecdsa::signature::Signer};
use secp256k1_zkp::{Tweak, constants::SECRET_KEY_SIZE};
use sha2::{Digest, digest::FixedOutput}; use sha2::{Digest, digest::FixedOutput};
use crate::{ use crate::{
merkle_tree_public::TreeHashType, TreeHashType,
transaction::{Transaction, TransactionBody, TxKind}, transaction::{EncodedTransaction, TxKind},
}; };
fn test_transaction_body() -> TransactionBody { fn test_transaction_body() -> EncodedTransaction {
TransactionBody { EncodedTransaction {
tx_kind: TxKind::Public, tx_kind: TxKind::Public,
execution_input: vec![1, 2, 3, 4], encoded_transaction_data: vec![1, 2, 3, 4],
execution_output: vec![5, 6, 7, 8],
utxo_commitments_spent_hashes: vec![[9; 32], [10; 32], [11; 32], [12; 32]],
utxo_commitments_created_hashes: vec![[13; 32]],
nullifier_created_hashes: vec![[0; 32], [1; 32], [2; 32], [3; 32]],
execution_proof_private: "loremipsum".to_string(),
encoded_data: vec![(vec![255, 255, 255], vec![254, 254, 254], 1)],
ephemeral_pub_key: vec![5; 32],
commitment: vec![],
tweak: Tweak::from_slice(&[7; SECRET_KEY_SIZE]).unwrap(),
secret_r: [8; 32],
sc_addr: "someAddress".to_string(),
} }
} }
fn test_transaction() -> Transaction {
let body = test_transaction_body();
let key_bytes = FieldBytes::from_slice(&[37; 32]);
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
Transaction::new(body, private_key)
}
#[test] #[test]
fn test_transaction_hash_is_sha256_of_json_bytes() { fn test_transaction_hash_is_sha256_of_json_bytes() {
let body = test_transaction_body(); let body = test_transaction_body();
let expected_hash = { let expected_hash = {
let data = serde_json::to_vec(&body).unwrap(); let data = body.to_bytes();
let mut hasher = sha2::Sha256::new(); let mut hasher = sha2::Sha256::new();
hasher.update(&data); hasher.update(&data);
TreeHashType::from(hasher.finalize_fixed()) TreeHashType::from(hasher.finalize_fixed())
@ -347,83 +233,12 @@ mod tests {
} }
#[test] #[test]
fn test_transaction_constructor() { fn test_to_bytes_from_bytes() {
let body = test_transaction_body(); let body = test_transaction_body();
let key_bytes = FieldBytes::from_slice(&[37; 32]);
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
let transaction = Transaction::new(body.clone(), private_key.clone());
assert_eq!(
transaction.public_key,
SignaturePublicKey::from(&private_key)
);
assert_eq!(transaction.body, body);
}
#[test] let body_bytes = body.to_bytes();
fn test_transaction_body_getter() { let body_new = EncodedTransaction::from_bytes(body_bytes);
let body = test_transaction_body();
let key_bytes = FieldBytes::from_slice(&[37; 32]);
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
let transaction = Transaction::new(body.clone(), private_key.clone());
assert_eq!(transaction.body(), &body);
}
#[test] assert_eq!(body, body_new);
fn test_into_authenticated_succeeds_for_valid_signature() {
let transaction = test_transaction();
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
let signature = authenticated_tx.transaction().signature;
let hash = authenticated_tx.hash();
assert_eq!(authenticated_tx.transaction(), &transaction);
assert_eq!(hash, &transaction.body.hash());
assert!(
authenticated_tx
.transaction()
.public_key
.verify(&transaction.body.to_bytes(), &signature)
.is_ok()
);
}
#[test]
fn test_into_authenticated_fails_for_invalid_signature() {
let body = test_transaction_body();
let key_bytes = FieldBytes::from_slice(&[37; 32]);
let private_key: SigningKey = SigningKey::from_bytes(key_bytes).unwrap();
let transaction = {
let mut this = Transaction::new(body, private_key.clone());
// Modify the signature to make it invalid
// We do this by changing it to the signature of something else
this.signature = private_key.sign(b"deadbeef");
this
};
matches!(
transaction.into_authenticated(),
Err(TransactionSignatureError::InvalidSignature)
);
}
#[test]
fn test_authenticated_transaction_getter() {
let transaction = test_transaction();
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
assert_eq!(authenticated_tx.transaction(), &transaction);
}
#[test]
fn test_authenticated_transaction_hash_getter() {
let transaction = test_transaction();
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
assert_eq!(authenticated_tx.hash(), &transaction.body.hash());
}
#[test]
fn test_authenticated_transaction_into_transaction() {
let transaction = test_transaction();
let authenticated_tx = transaction.clone().into_authenticated().unwrap();
assert_eq!(authenticated_tx.into_transaction(), transaction);
} }
} }

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::merkle_tree_public::TreeHashType; use crate::TreeHashType;
//ToDo: Update UTXO Commitment model, when it is clear //ToDo: Update UTXO Commitment model, when it is clear
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]

View File

@ -30,3 +30,6 @@ path = "../wallet"
[dependencies.common] [dependencies.common]
path = "../common" path = "../common"
[dependencies.nssa]
path = "../nssa"

View File

@ -15,5 +15,7 @@
"addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766", "addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"balance": 20000 "balance": 20000
} }
] ],
"signing_key": [37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37]
} }

View File

@ -5,6 +5,7 @@ use anyhow::Result;
use clap::Parser; use clap::Parser;
use common::sequencer_client::SequencerClient; use common::sequencer_client::SequencerClient;
use log::{info, warn}; use log::{info, warn};
use nssa::program::Program;
use sequencer_core::config::SequencerConfig; use sequencer_core::config::SequencerConfig;
use sequencer_runner::startup_sequencer; use sequencer_runner::startup_sequencer;
use tempfile::TempDir; use tempfile::TempDir;
@ -272,6 +273,25 @@ pub async fn test_success_two_transactions() {
info!("Second TX Success!"); info!("Second TX Success!");
} }
pub async fn test_get_account() {
let wallet_config = fetch_config().unwrap();
let seq_client = SequencerClient::new(wallet_config.sequencer_addr.clone()).unwrap();
let account = seq_client
.get_account(ACC_SENDER.to_string())
.await
.unwrap()
.account;
assert_eq!(
account.program_owner,
Program::authenticated_transfer_program().id()
);
assert_eq!(account.balance, 10000);
assert!(account.data.is_empty());
assert_eq!(account.nonce, 0);
}
pub async fn test_pinata() { pub async fn test_pinata() {
let pinata_addr = "cafe".repeat(16); let pinata_addr = "cafe".repeat(16);
let pinata_prize = 150; let pinata_prize = 150;
@ -348,6 +368,9 @@ pub async fn main_tests_runner() -> Result<()> {
"test_failure" => { "test_failure" => {
test_cleanup_wrap!(home_dir, test_failure); test_cleanup_wrap!(home_dir, test_failure);
} }
"test_get_account_wallet_command" => {
test_cleanup_wrap!(home_dir, test_get_account);
}
"test_success_two_transactions" => { "test_success_two_transactions" => {
test_cleanup_wrap!(home_dir, test_success_two_transactions); test_cleanup_wrap!(home_dir, test_success_two_transactions);
} }
@ -360,6 +383,7 @@ pub async fn main_tests_runner() -> Result<()> {
test_cleanup_wrap!(home_dir, test_failure); test_cleanup_wrap!(home_dir, test_failure);
test_cleanup_wrap!(home_dir, test_success_two_transactions); test_cleanup_wrap!(home_dir, test_success_two_transactions);
test_cleanup_wrap!(home_dir, test_pinata); test_cleanup_wrap!(home_dir, test_pinata);
test_cleanup_wrap!(home_dir, test_get_account);
} }
_ => { _ => {
anyhow::bail!("Unknown test name"); anyhow::bail!("Unknown test name");

View File

@ -1,4 +1,4 @@
use common::merkle_tree_public::TreeHashType; use common::TreeHashType;
use elliptic_curve::PrimeField; use elliptic_curve::PrimeField;
use k256::{AffinePoint, FieldBytes, Scalar}; use k256::{AffinePoint, FieldBytes, Scalar};
use rand::{RngCore, rngs::OsRng}; use rand::{RngCore, rngs::OsRng};

View File

@ -8,6 +8,8 @@ use crate::{NullifierPublicKey, account::Account};
pub struct Commitment(pub(super) [u8; 32]); pub struct Commitment(pub(super) [u8; 32]);
impl Commitment { impl Commitment {
/// Generates the commitment to a private account owned by user for npk:
/// SHA256(npk || program_owner || balance || nonce || data)
pub fn new(npk: &NullifierPublicKey, account: &Account) -> Self { pub fn new(npk: &NullifierPublicKey, account: &Account) -> Self {
let mut bytes = Vec::new(); let mut bytes = Vec::new();
bytes.extend_from_slice(&npk.to_byte_array()); bytes.extend_from_slice(&npk.to_byte_array());
@ -34,6 +36,7 @@ pub type CommitmentSetDigest = [u8; 32];
pub type MembershipProof = (usize, Vec<[u8; 32]>); pub type MembershipProof = (usize, Vec<[u8; 32]>);
/// Computes the resulting digest for the given membership proof and corresponding commitment
pub fn compute_digest_for_path( pub fn compute_digest_for_path(
commitment: &Commitment, commitment: &Commitment,
proof: &MembershipProof, proof: &MembershipProof,

View File

@ -32,11 +32,13 @@ fn main() {
// Check that the program is well behaved. // Check that the program is well behaved.
// See the # Programs section for the definition of the `validate_execution` method. // See the # Programs section for the definition of the `validate_execution` method.
validate_execution(&pre_states, &post_states, program_id); if !validate_execution(&pre_states, &post_states, program_id) {
panic!("Bad behaved program");
}
let n_accounts = pre_states.len(); let n_accounts = pre_states.len();
if visibility_mask.len() != n_accounts { if visibility_mask.len() != n_accounts {
panic!(); panic!("Invalid visibility mask length");
} }
// These lists will be the public outputs of this circuit // These lists will be the public outputs of this circuit
@ -59,7 +61,9 @@ fn main() {
public_pre_states.push(pre_states[i].clone()); public_pre_states.push(pre_states[i].clone());
let mut post = post_states[i].clone(); let mut post = post_states[i].clone();
post.nonce += 1; if pre_states[i].is_authorized {
post.nonce += 1;
}
if post.program_owner == DEFAULT_PROGRAM_ID { if post.program_owner == DEFAULT_PROGRAM_ID {
// Claim account // Claim account
post.program_owner = program_id; post.program_owner = program_id;
@ -136,7 +140,7 @@ fn main() {
} }
if private_keys_iter.next().is_some() { if private_keys_iter.next().is_some() {
panic!("Too many private accounts keys."); panic!("Too many private account keys.");
} }
if private_auth_iter.next().is_some() { if private_auth_iter.next().is_some() {

2
nssa/src/encoding/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod privacy_preserving_transaction;
pub mod public_transaction;

View File

@ -7,16 +7,20 @@ use nssa_core::{
}; };
use crate::{ use crate::{
Address, error::NssaError, privacy_preserving_transaction::message::EncryptedAccountData, Address, PrivacyPreservingTransaction, PublicKey, Signature,
error::NssaError,
privacy_preserving_transaction::{
circuit::Proof,
message::{EncryptedAccountData, Message},
witness_set::WitnessSet,
},
}; };
use super::message::Message;
const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; const MESSAGE_ENCODING_PREFIX_LEN: usize = 22;
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/"; const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/";
impl EncryptedAccountData { impl EncryptedAccountData {
pub(crate) fn to_bytes(&self) -> Vec<u8> { pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.ciphertext.to_bytes(); let mut bytes = self.ciphertext.to_bytes();
bytes.extend_from_slice(&self.epk.to_bytes()); bytes.extend_from_slice(&self.epk.to_bytes());
bytes.push(self.view_tag); bytes.push(self.view_tag);
@ -168,3 +172,86 @@ impl Message {
}) })
} }
} }
impl WitnessSet {
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let size = self.signatures_and_public_keys().len() as u32;
bytes.extend_from_slice(&size.to_le_bytes());
for (signature, public_key) in self.signatures_and_public_keys() {
bytes.extend_from_slice(signature.to_bytes());
bytes.extend_from_slice(public_key.to_bytes());
}
bytes.extend_from_slice(&self.proof.to_bytes());
bytes
}
pub(crate) fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let num_signatures: u32 = {
let mut buf = [0u8; 4];
cursor.read_exact(&mut buf)?;
u32::from_le_bytes(buf)
};
let mut signatures_and_public_keys = Vec::with_capacity(num_signatures as usize);
for _i in 0..num_signatures {
let signature = Signature::from_cursor(cursor)?;
let public_key = PublicKey::from_cursor(cursor)?;
signatures_and_public_keys.push((signature, public_key))
}
let proof = Proof::from_cursor(cursor)?;
Ok(Self {
signatures_and_public_keys,
proof,
})
}
}
impl PrivacyPreservingTransaction {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = self.message().to_bytes();
bytes.extend_from_slice(&self.witness_set().to_bytes());
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, NssaError> {
let mut cursor = Cursor::new(bytes);
Self::from_cursor(&mut cursor)
}
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let message = Message::from_cursor(cursor)?;
let witness_set = WitnessSet::from_cursor(cursor)?;
Ok(PrivacyPreservingTransaction::new(message, witness_set))
}
}
impl Proof {
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let proof_len = self.0.len() as u32;
bytes.extend_from_slice(&proof_len.to_le_bytes());
bytes.extend_from_slice(&self.0);
bytes
}
pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result<Self, NssaError> {
let proof_len = u32_from_cursor(cursor) as usize;
let mut proof = Vec::with_capacity(proof_len);
for _ in 0..proof_len {
let mut one_byte_buf = [0u8];
cursor.read_exact(&mut one_byte_buf)?;
proof.push(one_byte_buf[0]);
}
Ok(Self(proof))
}
}
// TODO: Improve error handling. Remove unwraps.
pub fn u32_from_cursor(cursor: &mut Cursor<&[u8]>) -> u32 {
let mut word_buf = [0u8; 4];
cursor.read_exact(&mut word_buf).unwrap();
u32::from_le_bytes(word_buf)
}

View File

@ -45,4 +45,7 @@ pub enum NssaError {
#[error("Invalid privacy preserving execution circuit proof")] #[error("Invalid privacy preserving execution circuit proof")]
InvalidPrivacyPreservingProof, InvalidPrivacyPreservingProof,
#[error("Circuit proving error")]
CircuitProvingError(String),
} }

View File

@ -1,4 +1,5 @@
pub mod address; pub mod address;
pub mod encoding;
pub mod error; pub mod error;
mod merkle_tree; mod merkle_tree;
mod privacy_preserving_transaction; mod privacy_preserving_transaction;
@ -8,6 +9,7 @@ mod signature;
mod state; mod state;
pub use address::Address; pub use address::Address;
pub use nssa_core::account::Account;
pub use privacy_preserving_transaction::{ pub use privacy_preserving_transaction::{
PrivacyPreservingTransaction, circuit::execute_and_prove, PrivacyPreservingTransaction, circuit::execute_and_prove,
}; };

View File

@ -39,11 +39,11 @@ impl MerkleTree {
if tree_depth == capacity_depth { if tree_depth == capacity_depth {
0 0
} else { } else {
// 2^(capacity_depth - tree_depth) - 1
(1 << (capacity_depth - tree_depth)) - 1 (1 << (capacity_depth - tree_depth)) - 1
} }
} }
/// Number of levels required to hold all nodes
/// Number of levels required to hold all values
fn depth(&self) -> usize { fn depth(&self) -> usize {
self.length.next_power_of_two().trailing_zeros() as usize self.length.next_power_of_two().trailing_zeros() as usize
} }
@ -57,6 +57,7 @@ impl MerkleTree {
} }
pub fn with_capacity(capacity: usize) -> Self { pub fn with_capacity(capacity: usize) -> Self {
// Adjust capacity to ensure power of two
let capacity = capacity.next_power_of_two(); let capacity = capacity.next_power_of_two();
let total_depth = capacity.trailing_zeros() as usize; let total_depth = capacity.trailing_zeros() as usize;
@ -75,6 +76,8 @@ impl MerkleTree {
} }
} }
/// Reallocates storage of Merkle tree for double capacity.
/// The current tree is embedded into the new tree as a subtree
fn reallocate_to_double_capacity(&mut self) { fn reallocate_to_double_capacity(&mut self) {
let old_capacity = self.capacity; let old_capacity = self.capacity;
let new_capacity = old_capacity << 1; let new_capacity = old_capacity << 1;
@ -102,9 +105,11 @@ impl MerkleTree {
let mut node_index = new_index + self.capacity - 1; let mut node_index = new_index + self.capacity - 1;
let mut node_hash = hash_value(&value); let mut node_hash = hash_value(&value);
// Insert the new node at the bottom layer
self.set_node(node_index, node_hash); self.set_node(node_index, node_hash);
self.length += 1; self.length += 1;
// Update upper levels for the newly inserted node
for _ in 0..self.depth() { for _ in 0..self.depth() {
let parent_index = (node_index - 1) >> 1; let parent_index = (node_index - 1) >> 1;
let left_child = self.get_node((parent_index << 1) + 1); let left_child = self.get_node((parent_index << 1) + 1);
@ -129,6 +134,7 @@ impl MerkleTree {
while node_index != root_index { while node_index != root_index {
let parent_index = (node_index - 1) >> 1; let parent_index = (node_index - 1) >> 1;
// Left children have odd indices, and right children have even indices
let is_left_child = node_index & 1 == 1; let is_left_child = node_index & 1 == 1;
let sibling_index = if is_left_child { let sibling_index = if is_left_child {
node_index + 1 node_index + 1

View File

@ -12,15 +12,7 @@ use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT
/// Proof of the privacy preserving execution circuit /// Proof of the privacy preserving execution circuit
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Proof(Vec<u8>); pub struct Proof(pub(crate) Vec<u8>);
impl Proof {
pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool {
let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap();
let receipt = Receipt::new(inner, circuit_output.to_bytes());
receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok()
}
}
/// Generates a proof of the execution of a NSSA program inside the privacy preserving execution /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution
/// circuit /// circuit
@ -55,7 +47,9 @@ pub fn execute_and_prove(
env_builder.write(&circuit_input).unwrap(); env_builder.write(&circuit_input).unwrap();
let env = env_builder.build().unwrap(); let env = env_builder.build().unwrap();
let prover = default_prover(); let prover = default_prover();
let prove_info = prover.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF).unwrap(); let prove_info = prover
.prove(env, PRIVACY_PRESERVING_CIRCUIT_ELF)
.map_err(|e| NssaError::CircuitProvingError(e.to_string()))?;
let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?); let proof = Proof(borsh::to_vec(&prove_info.receipt.inner)?);
@ -86,6 +80,14 @@ fn execute_and_prove_program(
.receipt) .receipt)
} }
impl Proof {
pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool {
let inner: InnerReceipt = borsh::from_slice(&self.0).unwrap();
let receipt = Receipt::new(inner, circuit_output.to_bytes());
receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use nssa_core::{ use nssa_core::{
@ -109,6 +111,7 @@ mod tests {
let program = Program::authenticated_transfer_program(); let program = Program::authenticated_transfer_program();
let sender = AccountWithMetadata { let sender = AccountWithMetadata {
account: Account { account: Account {
program_owner: program.id(),
balance: 100, balance: 100,
..Account::default() ..Account::default()
}, },
@ -175,11 +178,13 @@ mod tests {
#[test] #[test]
fn prove_privacy_preserving_execution_circuit_fully_private() { fn prove_privacy_preserving_execution_circuit_fully_private() {
let program = Program::authenticated_transfer_program();
let sender_pre = AccountWithMetadata { let sender_pre = AccountWithMetadata {
account: Account { account: Account {
balance: 100, balance: 100,
nonce: 0xdeadbeef, nonce: 0xdeadbeef,
..Account::default() program_owner: program.id(),
data: vec![],
}, },
is_authorized: true, is_authorized: true,
}; };

View File

@ -1,4 +1,3 @@
mod encoding;
pub mod message; pub mod message;
pub mod transaction; pub mod transaction;
pub mod witness_set; pub mod witness_set;

View File

@ -167,3 +167,42 @@ fn n_unique<T: Eq + Hash>(data: &[T]) -> usize {
let set: HashSet<&T> = data.iter().collect(); let set: HashSet<&T> = data.iter().collect();
set.len() set.len()
} }
#[cfg(test)]
mod tests {
use crate::{
Address, PrivacyPreservingTransaction, PrivateKey, PublicKey,
privacy_preserving_transaction::{
circuit::Proof, message::tests::message_for_tests, witness_set::WitnessSet,
},
};
fn keys_for_tests() -> (PrivateKey, PrivateKey, Address, Address) {
let key1 = PrivateKey::try_new([1; 32]).unwrap();
let key2 = PrivateKey::try_new([2; 32]).unwrap();
let addr1 = Address::from(&PublicKey::new_from_private_key(&key1));
let addr2 = Address::from(&PublicKey::new_from_private_key(&key2));
(key1, key2, addr1, addr2)
}
fn proof_for_tests() -> Proof {
Proof(vec![1, 2, 3, 4, 5])
}
fn transaction_for_tests() -> PrivacyPreservingTransaction {
let (key1, key2, _, _) = keys_for_tests();
let message = message_for_tests();
let witness_set = WitnessSet::for_message(&message, proof_for_tests(), &[&key1, &key2]);
PrivacyPreservingTransaction::new(message, witness_set)
}
#[test]
fn test_privacy_preserving_transaction_encoding_bytes_roundtrip() {
let tx = transaction_for_tests();
let bytes = tx.to_bytes();
let tx_from_bytes = PrivacyPreservingTransaction::from_bytes(&bytes).unwrap();
assert_eq!(tx, tx_from_bytes);
}
}

View File

@ -5,8 +5,8 @@ use crate::{
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct WitnessSet { pub struct WitnessSet {
pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>, pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
pub(super) proof: Proof, pub(crate) proof: Proof,
} }
impl WitnessSet { impl WitnessSet {

View File

@ -1,4 +1,3 @@
mod encoding;
mod message; mod message;
mod transaction; mod transaction;
mod witness_set; mod witness_set;

View File

@ -2,7 +2,7 @@ use crate::{PrivateKey, PublicKey, Signature, public_transaction::Message};
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct WitnessSet { pub struct WitnessSet {
pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>, pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
} }
impl WitnessSet { impl WitnessSet {

View File

@ -21,6 +21,7 @@ impl CommitmentSet {
self.merkle_tree.root() self.merkle_tree.root()
} }
/// Queries the `CommitmentSet` for a membership proof of commitment
pub fn get_proof_for(&self, commitment: &Commitment) -> Option<MembershipProof> { pub fn get_proof_for(&self, commitment: &Commitment) -> Option<MembershipProof> {
let index = *self.commitments.get(commitment)?; let index = *self.commitments.get(commitment)?;
@ -29,6 +30,7 @@ impl CommitmentSet {
.map(|path| (index, path)) .map(|path| (index, path))
} }
/// Inserts a list of commitments to the `CommitmentSet`.
pub(crate) fn extend(&mut self, commitments: &[Commitment]) { pub(crate) fn extend(&mut self, commitments: &[Commitment]) {
for commitment in commitments.iter().cloned() { for commitment in commitments.iter().cloned() {
let index = self.merkle_tree.insert(commitment.to_byte_array()); let index = self.merkle_tree.insert(commitment.to_byte_array());
@ -41,6 +43,9 @@ impl CommitmentSet {
self.commitments.contains_key(commitment) self.commitments.contains_key(commitment)
} }
/// Initializes an empty `CommitmentSet` with a given capacity.
/// If the capacity is not a power_of_two, then capacity is taken
/// to be the next power_of_two.
pub(crate) fn with_capacity(capacity: usize) -> CommitmentSet { pub(crate) fn with_capacity(capacity: usize) -> CommitmentSet {
Self { Self {
merkle_tree: MerkleTree::with_capacity(capacity), merkle_tree: MerkleTree::with_capacity(capacity),
@ -140,12 +145,6 @@ impl V01State {
*current_account = post; *current_account = post;
} }
// // 5. Increment nonces
for address in tx.signer_addresses() {
let current_account = self.get_account_by_address_mut(address);
current_account.nonce += 1;
}
Ok(()) Ok(())
} }
@ -232,6 +231,7 @@ pub mod tests {
use crate::{ use crate::{
Address, PublicKey, PublicTransaction, V01State, Address, PublicKey, PublicTransaction, V01State,
error::NssaError, error::NssaError,
execute_and_prove,
privacy_preserving_transaction::{ privacy_preserving_transaction::{
PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet, PrivacyPreservingTransaction, circuit, message::Message, witness_set::WitnessSet,
}, },
@ -954,6 +954,13 @@ pub mod tests {
&state, &state,
); );
let expected_sender_post = {
let mut this = state.get_account_by_address(&sender_keys.address());
this.balance -= balance_to_move;
this.nonce += 1;
this
};
let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap(); let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap();
assert!(!state.private_state.0.contains(&expected_new_commitment)); assert!(!state.private_state.0.contains(&expected_new_commitment));
@ -961,6 +968,8 @@ pub mod tests {
.transition_from_privacy_preserving_transaction(&tx) .transition_from_privacy_preserving_transaction(&tx)
.unwrap(); .unwrap();
let sender_post = state.get_account_by_address(&sender_keys.address());
assert_eq!(sender_post, expected_sender_post);
assert!(state.private_state.0.contains(&expected_new_commitment)); assert!(state.private_state.0.contains(&expected_new_commitment));
assert_eq!( assert_eq!(
@ -1053,6 +1062,12 @@ pub mod tests {
let balance_to_move = 37; let balance_to_move = 37;
let expected_recipient_post = {
let mut this = state.get_account_by_address(&recipient_keys.address());
this.balance += balance_to_move;
this
};
let tx = deshielded_balance_transfer_for_tests( let tx = deshielded_balance_transfer_for_tests(
&sender_keys, &sender_keys,
&sender_private_account, &sender_private_account,
@ -1083,6 +1098,8 @@ pub mod tests {
.transition_from_privacy_preserving_transaction(&tx) .transition_from_privacy_preserving_transaction(&tx)
.unwrap(); .unwrap();
let recipient_post = state.get_account_by_address(&recipient_keys.address());
assert_eq!(recipient_post, expected_recipient_post);
assert!(state.private_state.0.contains(&sender_pre_commitment)); assert!(state.private_state.0.contains(&sender_pre_commitment));
assert!(state.private_state.0.contains(&expected_new_commitment)); assert!(state.private_state.0.contains(&expected_new_commitment));
assert!(state.private_state.1.contains(&expected_new_nullifier)); assert!(state.private_state.1.contains(&expected_new_nullifier));
@ -1093,4 +1110,809 @@ pub mod tests {
recipient_initial_balance + balance_to_move recipient_initial_balance + balance_to_move
); );
} }
#[test]
fn test_burner_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::burner();
let public_account = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(10u128).unwrap(),
&[0],
&[],
&[],
&[],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_minter_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::minter();
let public_account = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(10u128).unwrap(),
&[0],
&[],
&[],
&[],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_nonce_changer_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::nonce_changer_program();
let public_account = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(()).unwrap(),
&[0],
&[],
&[],
&[],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_data_changer_program_should_fail_for_non_owned_account_in_privacy_preserving_circuit() {
let program = Program::data_changer();
let public_account = AccountWithMetadata {
account: Account {
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
balance: 0,
..Account::default()
},
is_authorized: true,
};
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(()).unwrap(),
&[0],
&[],
&[],
&[],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_extra_output_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::extra_output_program();
let public_account = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(()).unwrap(),
&[0],
&[],
&[],
&[],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_missing_output_program_should_fail_in_privacy_preserving_circuit() {
let program = Program::missing_output_program();
let public_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
let public_account_2 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
let result = execute_and_prove(
&[public_account_1, public_account_2],
&Program::serialize_instruction(()).unwrap(),
&[0, 0],
&[],
&[],
&[],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_program_owner_changer_should_fail_in_privacy_preserving_circuit() {
let program = Program::program_owner_changer();
let public_account = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
let result = execute_and_prove(
&[public_account],
&Program::serialize_instruction(()).unwrap(),
&[0],
&[],
&[],
&[],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_transfer_from_non_owned_account_should_fail_in_privacy_preserving_circuit() {
let program = Program::simple_balance_transfer();
let public_account_1 = AccountWithMetadata {
account: Account {
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
balance: 100,
..Account::default()
},
is_authorized: true,
};
let public_account_2 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
let result = execute_and_prove(
&[public_account_1, public_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[0, 0],
&[],
&[],
&[],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_fails_if_visibility_masks_have_incorrect_lenght() {
let program = Program::simple_balance_transfer();
let public_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let public_account_2 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 0,
..Account::default()
},
is_authorized: true,
};
// Setting only one visibility mask for a circuit execution with two pre_state accounts.
let visibility_mask = [0];
let result = execute_and_prove(
&[public_account_1, public_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&visibility_mask,
&[],
&[],
&[],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_fails_if_insufficient_nonces_are_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
// Setting only one nonce for an execution with two private accounts.
let private_account_nonces = [0xdeadbeef1];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&private_account_nonces,
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_fails_if_insufficient_keys_are_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
// Setting only one key for an execution with two private accounts.
let private_account_keys = [(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
)];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&private_account_keys,
&[(sender_keys.nsk, (0, vec![]))],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_fails_if_insufficient_auth_keys_are_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
// Setting no auth key for an execution with one non default private accounts.
let private_account_auth = [];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&private_account_auth,
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_fails_if_invalid_auth_keys_are_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let private_account_keys = [
// First private account is the sender
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
// Second private account is the recipient
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
];
let private_account_auth = [
// Setting the recipient key to authorize the sender.
// This should be set to the sender private account in
// a normal circumstance. The recipient can't authorize this.
(recipient_keys.nsk, (0, vec![])),
];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&private_account_keys,
&private_account_auth,
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_if_new_private_account_with_non_default_balance_is_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account {
// Non default balance
balance: 1,
..Account::default()
},
is_authorized: false,
};
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_provided()
{
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account {
// Non default program_owner
program_owner: [0, 1, 2, 3, 4, 5, 6, 7],
..Account::default()
},
is_authorized: false,
};
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_if_new_private_account_with_non_default_data_is_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account {
// Non default data
data: b"hola mundo".to_vec(),
..Account::default()
},
is_authorized: false,
};
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account {
// Non default nonce
nonce: 0xdeadbeef,
..Account::default()
},
is_authorized: false,
};
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_if_new_private_account_is_provided_with_default_values_but_marked_as_authorized()
{
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
// This should be set to false in normal circumstances
is_authorized: true,
};
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_with_invalid_visibility_mask_value() {
let program = Program::simple_balance_transfer();
let public_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let public_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
let visibility_mask = [0, 3];
let result = execute_and_prove(
&[public_account_1, public_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&visibility_mask,
&[],
&[],
&[],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_with_too_many_nonces() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
// Setting three new private account nonces for a circuit execution with only two private
// accounts.
let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&private_account_nonces,
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&[(sender_keys.nsk, (0, vec![]))],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_with_too_many_private_account_keys() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
// Setting three private account keys for a circuit execution with only two private
// accounts.
let private_account_keys = [
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
(
sender_keys.npk(),
SharedSecretKey::new(&[57; 32], &sender_keys.ivk()),
),
];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&[1, 2],
&[0xdeadbeef1, 0xdeadbeef2],
&private_account_keys,
&[(sender_keys.nsk, (0, vec![]))],
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
#[test]
fn test_circuit_should_fail_with_too_many_private_account_auth_keys() {
let program = Program::simple_balance_transfer();
let sender_keys = test_private_account_keys_1();
let recipient_keys = test_private_account_keys_2();
let private_account_1 = AccountWithMetadata {
account: Account {
program_owner: program.id(),
balance: 100,
..Account::default()
},
is_authorized: true,
};
let private_account_2 = AccountWithMetadata {
account: Account::default(),
is_authorized: false,
};
// Setting two private account keys for a circuit execution with only one non default
// private account (visibility mask equal to 1 means that auth keys are expected).
let visibility_mask = [1, 2];
let private_account_auth = [
(sender_keys.nsk, (0, vec![])),
(recipient_keys.nsk, (1, vec![])),
];
let result = execute_and_prove(
&[private_account_1, private_account_2],
&Program::serialize_instruction(10u128).unwrap(),
&visibility_mask,
&[0xdeadbeef1, 0xdeadbeef2],
&[
(
sender_keys.npk(),
SharedSecretKey::new(&[55; 32], &sender_keys.ivk()),
),
(
recipient_keys.npk(),
SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()),
),
],
&private_account_auth,
&program,
);
assert!(matches!(result, Err(NssaError::CircuitProvingError(_))));
}
} }

View File

@ -5,12 +5,12 @@ type Instruction = ();
fn main() { fn main() {
let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>(); let ProgramInput { pre_states, .. } = read_nssa_inputs::<Instruction>();
let [pre1, _] = match pre_states.try_into() { let [pre1, pre2] = match pre_states.try_into() {
Ok(array) => array, Ok(array) => array,
Err(_) => return, Err(_) => return,
}; };
let account_pre1 = pre1.account.clone(); let account_pre1 = pre1.account.clone();
write_nssa_outputs(vec![pre1], vec![account_pre1]); write_nssa_outputs(vec![pre1, pre2], vec![account_pre1]);
} }

View File

@ -9,6 +9,7 @@ anyhow.workspace = true
serde.workspace = true serde.workspace = true
rand.workspace = true rand.workspace = true
tempfile.workspace = true tempfile.workspace = true
chrono.workspace = true
log.workspace = true log.workspace = true
[dependencies.storage] [dependencies.storage]

View File

@ -27,4 +27,6 @@ pub struct SequencerConfig {
pub port: u16, pub port: u16,
///List of initial accounts data ///List of initial accounts data
pub initial_accounts: Vec<AccountInitialData>, pub initial_accounts: Vec<AccountInitialData>,
///Sequencer own signing key
pub signing_key: [u8; 32],
} }

View File

@ -1,7 +1,11 @@
use std::fmt::Display; use std::fmt::Display;
use anyhow::Result; use anyhow::Result;
use common::{block::HashableBlockData, merkle_tree_public::TreeHashType}; use common::{
TreeHashType,
block::HashableBlockData,
transaction::{EncodedTransaction, NSSATransaction},
};
use config::SequencerConfig; use config::SequencerConfig;
use log::warn; use log::warn;
use mempool::MemPool; use mempool::MemPool;
@ -13,7 +17,7 @@ pub mod sequencer_store;
pub struct SequencerCore { pub struct SequencerCore {
pub store: SequecerChainStore, pub store: SequecerChainStore,
pub mempool: MemPool<nssa::PublicTransaction>, pub mempool: MemPool<EncodedTransaction>,
pub sequencer_config: SequencerConfig, pub sequencer_config: SequencerConfig,
pub chain_height: u64, pub chain_height: u64,
} }
@ -51,6 +55,7 @@ impl SequencerCore {
config.genesis_id, config.genesis_id,
config.is_genesis_random, config.is_genesis_random,
&config.initial_accounts, &config.initial_accounts,
nssa::PrivateKey::try_new(config.signing_key).unwrap(),
), ),
mempool: MemPool::default(), mempool: MemPool::default(),
chain_height: config.genesis_id, chain_height: config.genesis_id,
@ -60,20 +65,37 @@ impl SequencerCore {
pub fn transaction_pre_check( pub fn transaction_pre_check(
&mut self, &mut self,
tx: nssa::PublicTransaction, tx: NSSATransaction,
) -> Result<nssa::PublicTransaction, TransactionMalformationErrorKind> { ) -> Result<NSSATransaction, TransactionMalformationErrorKind> {
// Stateless checks here // Stateless checks here
if tx.witness_set().is_valid_for(tx.message()) { match tx {
Ok(tx) NSSATransaction::Public(tx) => {
} else { if tx.witness_set().is_valid_for(tx.message()) {
Err(TransactionMalformationErrorKind::InvalidSignature) Ok(NSSATransaction::Public(tx))
} else {
Err(TransactionMalformationErrorKind::InvalidSignature)
}
}
NSSATransaction::PrivacyPreserving(tx) => {
if tx.witness_set().signatures_are_valid_for(tx.message()) {
Ok(NSSATransaction::PrivacyPreserving(tx))
} else {
Err(TransactionMalformationErrorKind::InvalidSignature)
}
}
} }
} }
pub fn push_tx_into_mempool_pre_check( pub fn push_tx_into_mempool_pre_check(
&mut self, &mut self,
transaction: nssa::PublicTransaction, transaction: EncodedTransaction,
) -> Result<(), TransactionMalformationErrorKind> { ) -> Result<(), TransactionMalformationErrorKind> {
let transaction = NSSATransaction::try_from(&transaction).map_err(|_| {
TransactionMalformationErrorKind::FailedToDecode {
tx: transaction.hash(),
}
})?;
let mempool_size = self.mempool.len(); let mempool_size = self.mempool.len();
if mempool_size >= self.sequencer_config.max_num_tx_in_block { if mempool_size >= self.sequencer_config.max_num_tx_in_block {
return Err(TransactionMalformationErrorKind::MempoolFullForRound); return Err(TransactionMalformationErrorKind::MempoolFullForRound);
@ -83,19 +105,29 @@ impl SequencerCore {
.transaction_pre_check(transaction) .transaction_pre_check(transaction)
.inspect_err(|err| warn!("Error at pre_check {err:#?}"))?; .inspect_err(|err| warn!("Error at pre_check {err:#?}"))?;
self.mempool.push_item(authenticated_tx); self.mempool.push_item(authenticated_tx.into());
Ok(()) Ok(())
} }
fn execute_check_transaction_on_state( fn execute_check_transaction_on_state(
&mut self, &mut self,
tx: nssa::PublicTransaction, tx: NSSATransaction,
) -> Result<nssa::PublicTransaction, nssa::error::NssaError> { ) -> Result<NSSATransaction, nssa::error::NssaError> {
self.store match &tx {
.state NSSATransaction::Public(tx) => {
.transition_from_public_transaction(&tx) self.store
.inspect_err(|err| warn!("Error at transition {err:#?}"))?; .state
.transition_from_public_transaction(tx)
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
}
NSSATransaction::PrivacyPreserving(tx) => {
self.store
.state
.transition_from_privacy_preserving_transaction(tx)
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
}
}
Ok(tx) Ok(tx)
} }
@ -104,29 +136,41 @@ impl SequencerCore {
pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result<u64> { pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result<u64> {
let new_block_height = self.chain_height + 1; let new_block_height = self.chain_height + 1;
let transactions = self let mut num_valid_transactions_in_block = 0;
.mempool let mut valid_transactions = vec![];
.pop_size(self.sequencer_config.max_num_tx_in_block);
let valid_transactions: Vec<_> = transactions while let Some(tx) = self.mempool.pop_last() {
.into_iter() let nssa_transaction = NSSATransaction::try_from(&tx)
.filter_map(|tx| self.execute_check_transaction_on_state(tx).ok()) .map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx.hash() })?;
.collect();
if let Ok(valid_tx) = self.execute_check_transaction_on_state(nssa_transaction) {
valid_transactions.push(valid_tx.into());
num_valid_transactions_in_block += 1;
if num_valid_transactions_in_block >= self.sequencer_config.max_num_tx_in_block {
break;
}
}
}
let prev_block_hash = self let prev_block_hash = self
.store .store
.block_store .block_store
.get_block_at_id(self.chain_height)? .get_block_at_id(self.chain_height)?
.header
.hash; .hash;
let curr_time = chrono::Utc::now().timestamp_millis() as u64;
let hashable_data = HashableBlockData { let hashable_data = HashableBlockData {
block_id: new_block_height, block_id: new_block_height,
prev_block_id: self.chain_height,
transactions: valid_transactions, transactions: valid_transactions,
prev_block_hash, prev_block_hash,
timestamp: curr_time,
}; };
let block = hashable_data.into(); let block = hashable_data.into_block(&self.store.block_store.signing_key);
self.store.block_store.put_block_at_id(block)?; self.store.block_store.put_block_at_id(block)?;
@ -138,10 +182,18 @@ impl SequencerCore {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use common::test_utils::sequencer_sign_key_for_testing;
use crate::config::AccountInitialData; use crate::config::AccountInitialData;
use super::*; use super::*;
fn parse_unwrap_tx_body_into_nssa_tx(tx_body: EncodedTransaction) -> NSSATransaction {
NSSATransaction::try_from(&tx_body)
.map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx_body.hash() })
.unwrap()
}
fn setup_sequencer_config_variable_initial_accounts( fn setup_sequencer_config_variable_initial_accounts(
initial_accounts: Vec<AccountInitialData>, initial_accounts: Vec<AccountInitialData>,
) -> SequencerConfig { ) -> SequencerConfig {
@ -157,6 +209,7 @@ mod tests {
block_create_timeout_millis: 1000, block_create_timeout_millis: 1000,
port: 8080, port: 8080,
initial_accounts, initial_accounts,
signing_key: *sequencer_sign_key_for_testing().value(),
} }
} }
@ -298,7 +351,7 @@ mod tests {
common_setup(&mut sequencer); common_setup(&mut sequencer);
let tx = common::test_utils::produce_dummy_empty_transaction(); let tx = common::test_utils::produce_dummy_empty_transaction();
let result = sequencer.transaction_pre_check(tx); let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx));
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -324,7 +377,7 @@ mod tests {
let tx = common::test_utils::create_transaction_native_token_transfer( let tx = common::test_utils::create_transaction_native_token_transfer(
acc1, 0, acc2, 10, sign_key1, acc1, 0, acc2, 10, sign_key1,
); );
let result = sequencer.transaction_pre_check(tx); let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx));
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -352,7 +405,9 @@ mod tests {
); );
// Signature is valid, stateless check pass // Signature is valid, stateless check pass
let tx = sequencer.transaction_pre_check(tx).unwrap(); let tx = sequencer
.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx))
.unwrap();
// Signature is not from sender. Execution fails // Signature is not from sender. Execution fails
let result = sequencer.execute_check_transaction_on_state(tx); let result = sequencer.execute_check_transaction_on_state(tx);
@ -385,7 +440,7 @@ mod tests {
acc1, 0, acc2, 10000000, sign_key1, acc1, 0, acc2, 10000000, sign_key1,
); );
let result = sequencer.transaction_pre_check(tx); let result = sequencer.transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx));
//Passed pre-check //Passed pre-check
assert!(result.is_ok()); assert!(result.is_ok());
@ -421,7 +476,9 @@ mod tests {
acc1, 0, acc2, 100, sign_key1, acc1, 0, acc2, 100, sign_key1,
); );
sequencer.execute_check_transaction_on_state(tx).unwrap(); sequencer
.execute_check_transaction_on_state(parse_unwrap_tx_body_into_nssa_tx(tx))
.unwrap();
let bal_from = sequencer let bal_from = sequencer
.store .store
@ -528,7 +585,7 @@ mod tests {
.unwrap(); .unwrap();
// Only one should be included in the block // Only one should be included in the block
assert_eq!(block.transactions, vec![tx.clone()]); assert_eq!(block.body.transactions, vec![tx.clone()]);
} }
#[test] #[test]
@ -563,7 +620,7 @@ mod tests {
.block_store .block_store
.get_block_at_id(current_height) .get_block_at_id(current_height)
.unwrap(); .unwrap();
assert_eq!(block.transactions, vec![tx.clone()]); assert_eq!(block.body.transactions, vec![tx.clone()]);
// Add same transaction should fail // Add same transaction should fail
sequencer.mempool.push_item(tx); sequencer.mempool.push_item(tx);
@ -575,6 +632,6 @@ mod tests {
.block_store .block_store
.get_block_at_id(current_height) .get_block_at_id(current_height)
.unwrap(); .unwrap();
assert!(block.transactions.is_empty()); assert!(block.body.transactions.is_empty());
} }
} }

View File

@ -1,7 +1,7 @@
use std::{collections::HashMap, path::Path}; use std::{collections::HashMap, path::Path};
use anyhow::Result; use anyhow::Result;
use common::{block::Block, merkle_tree_public::TreeHashType}; use common::{TreeHashType, block::Block, transaction::EncodedTransaction};
use storage::RocksDBIO; use storage::RocksDBIO;
pub struct SequecerBlockStore { pub struct SequecerBlockStore {
@ -9,6 +9,7 @@ pub struct SequecerBlockStore {
// TODO: Consider adding the hashmap to the database for faster recovery. // TODO: Consider adding the hashmap to the database for faster recovery.
tx_hash_to_block_map: HashMap<TreeHashType, u64>, tx_hash_to_block_map: HashMap<TreeHashType, u64>,
pub genesis_id: u64, pub genesis_id: u64,
pub signing_key: nssa::PrivateKey,
} }
impl SequecerBlockStore { impl SequecerBlockStore {
@ -16,7 +17,11 @@ impl SequecerBlockStore {
/// Creates files if necessary. /// Creates files if necessary.
/// ///
/// ATTENTION: Will overwrite genesis block. /// ATTENTION: Will overwrite genesis block.
pub fn open_db_with_genesis(location: &Path, genesis_block: Option<Block>) -> Result<Self> { pub fn open_db_with_genesis(
location: &Path,
genesis_block: Option<Block>,
signing_key: nssa::PrivateKey,
) -> Result<Self> {
let tx_hash_to_block_map = if let Some(block) = &genesis_block { let tx_hash_to_block_map = if let Some(block) = &genesis_block {
block_to_transactions_map(block) block_to_transactions_map(block)
} else { } else {
@ -31,16 +36,17 @@ impl SequecerBlockStore {
dbio, dbio,
genesis_id, genesis_id,
tx_hash_to_block_map, tx_hash_to_block_map,
signing_key,
}) })
} }
///Reopening existing database ///Reopening existing database
pub fn open_db_restart(location: &Path) -> Result<Self> { pub fn open_db_restart(location: &Path, signing_key: nssa::PrivateKey) -> Result<Self> {
SequecerBlockStore::open_db_with_genesis(location, None) SequecerBlockStore::open_db_with_genesis(location, None, signing_key)
} }
pub fn get_block_at_id(&self, id: u64) -> Result<Block> { pub fn get_block_at_id(&self, id: u64) -> Result<Block> {
Ok(self.dbio.get_block(id)?) Ok(self.dbio.get_block(id)?.into_block(&self.signing_key))
} }
pub fn put_block_at_id(&mut self, block: Block) -> Result<()> { pub fn put_block_at_id(&mut self, block: Block) -> Result<()> {
@ -51,11 +57,11 @@ impl SequecerBlockStore {
} }
/// Returns the transaction corresponding to the given hash, if it exists in the blockchain. /// Returns the transaction corresponding to the given hash, if it exists in the blockchain.
pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option<nssa::PublicTransaction> { pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option<EncodedTransaction> {
let block_id = self.tx_hash_to_block_map.get(&hash); let block_id = self.tx_hash_to_block_map.get(&hash);
let block = block_id.map(|&id| self.get_block_at_id(id)); let block = block_id.map(|&id| self.get_block_at_id(id));
if let Some(Ok(block)) = block { if let Some(Ok(block)) = block {
for transaction in block.transactions.into_iter() { for transaction in block.body.transactions.into_iter() {
if transaction.hash() == hash { if transaction.hash() == hash {
return Some(transaction); return Some(transaction);
} }
@ -67,9 +73,10 @@ impl SequecerBlockStore {
fn block_to_transactions_map(block: &Block) -> HashMap<TreeHashType, u64> { fn block_to_transactions_map(block: &Block) -> HashMap<TreeHashType, u64> {
block block
.body
.transactions .transactions
.iter() .iter()
.map(|transaction| (transaction.hash(), block.block_id)) .map(|transaction| (transaction.hash(), block.header.block_id))
.collect() .collect()
} }
@ -77,22 +84,28 @@ fn block_to_transactions_map(block: &Block) -> HashMap<TreeHashType, u64> {
mod tests { mod tests {
use super::*; use super::*;
use common::{block::HashableBlockData, test_utils::sequencer_sign_key_for_testing};
use tempfile::tempdir; use tempfile::tempdir;
#[test] #[test]
fn test_get_transaction_by_hash() { fn test_get_transaction_by_hash() {
let temp_dir = tempdir().unwrap(); let temp_dir = tempdir().unwrap();
let path = temp_dir.path(); let path = temp_dir.path();
let genesis_block = Block {
let signing_key = sequencer_sign_key_for_testing();
let genesis_block_hashable_data = HashableBlockData {
block_id: 0, block_id: 0,
prev_block_id: 0,
prev_block_hash: [0; 32], prev_block_hash: [0; 32],
hash: [1; 32], timestamp: 0,
transactions: vec![], transactions: vec![],
}; };
let genesis_block = genesis_block_hashable_data.into_block(&signing_key);
// Start an empty node store // Start an empty node store
let mut node_store = let mut node_store =
SequecerBlockStore::open_db_with_genesis(path, Some(genesis_block)).unwrap(); SequecerBlockStore::open_db_with_genesis(path, Some(genesis_block), signing_key)
.unwrap();
let tx = common::test_utils::produce_dummy_empty_transaction(); let tx = common::test_utils::produce_dummy_empty_transaction();
let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]); let block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]);

View File

@ -20,6 +20,7 @@ impl SequecerChainStore {
genesis_id: u64, genesis_id: u64,
is_genesis_random: bool, is_genesis_random: bool,
initial_accounts: &[AccountInitialData], initial_accounts: &[AccountInitialData],
signing_key: nssa::PrivateKey,
) -> Self { ) -> Self {
let init_accs: Vec<(Address, u128)> = initial_accounts let init_accs: Vec<(Address, u128)> = initial_accounts
.iter() .iter()
@ -44,20 +45,23 @@ impl SequecerChainStore {
OsRng.fill_bytes(&mut prev_block_hash); OsRng.fill_bytes(&mut prev_block_hash);
} }
let curr_time = chrono::Utc::now().timestamp_millis() as u64;
let hashable_data = HashableBlockData { let hashable_data = HashableBlockData {
block_id: genesis_id, block_id: genesis_id,
prev_block_id: genesis_id.saturating_sub(1),
transactions: vec![], transactions: vec![],
prev_block_hash, prev_block_hash,
timestamp: curr_time,
}; };
let genesis_block = hashable_data.into(); let genesis_block = hashable_data.into_block(&signing_key);
//Sequencer should panic if unable to open db, //Sequencer should panic if unable to open db,
//as fixing this issue may require actions non-native to program scope //as fixing this issue may require actions non-native to program scope
let block_store = SequecerBlockStore::open_db_with_genesis( let block_store = SequecerBlockStore::open_db_with_genesis(
&home_dir.join("rocksdb"), &home_dir.join("rocksdb"),
Some(genesis_block), Some(genesis_block),
signing_key,
) )
.unwrap(); .unwrap();

View File

@ -5,19 +5,20 @@ use sequencer_core::config::AccountInitialData;
use serde_json::Value; use serde_json::Value;
use common::{ use common::{
TreeHashType,
block::HashableBlockData, block::HashableBlockData,
merkle_tree_public::TreeHashType,
rpc_primitives::{ rpc_primitives::{
errors::RpcError, errors::RpcError,
message::{Message, Request}, message::{Message, Request},
parser::RpcRequest, parser::RpcRequest,
requests::{ requests::{
GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountDataRequest, GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountRequest,
GetAccountDataResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse, GetAccountResponse, GetAccountsNoncesRequest, GetAccountsNoncesResponse,
GetInitialTestnetAccountsRequest, GetTransactionByHashRequest, GetInitialTestnetAccountsRequest, GetTransactionByHashRequest,
GetTransactionByHashResponse, GetTransactionByHashResponse,
}, },
}, },
transaction::EncodedTransaction,
}; };
use common::rpc_primitives::requests::{ use common::rpc_primitives::requests::{
@ -36,7 +37,7 @@ pub const GET_LAST_BLOCK: &str = "get_last_block";
pub const GET_ACCOUNT_BALANCE: &str = "get_account_balance"; pub const GET_ACCOUNT_BALANCE: &str = "get_account_balance";
pub const GET_TRANSACTION_BY_HASH: &str = "get_transaction_by_hash"; pub const GET_TRANSACTION_BY_HASH: &str = "get_transaction_by_hash";
pub const GET_ACCOUNTS_NONCES: &str = "get_accounts_nonces"; pub const GET_ACCOUNTS_NONCES: &str = "get_accounts_nonces";
pub const GET_ACCOUNT_DATA: &str = "get_account_data"; pub const GET_ACCOUNT: &str = "get_account";
pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER"; pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER";
@ -74,8 +75,7 @@ impl JsonHandler {
async fn process_send_tx(&self, request: Request) -> Result<Value, RpcErr> { async fn process_send_tx(&self, request: Request) -> Result<Value, RpcErr> {
let send_tx_req = SendTxRequest::parse(Some(request.params))?; let send_tx_req = SendTxRequest::parse(Some(request.params))?;
let tx = nssa::PublicTransaction::from_bytes(&send_tx_req.transaction) let tx = EncodedTransaction::from_bytes(send_tx_req.transaction);
.map_err(|e| RpcError::serialization_error(&e.to_string()))?;
let tx_hash = hex::encode(tx.hash()); let tx_hash = hex::encode(tx.hash());
{ {
@ -204,10 +204,10 @@ impl JsonHandler {
respond(helperstruct) respond(helperstruct)
} }
///Returns account struct for give address. /// Returns account struct for given address.
/// Address must be a valid hex string of the correct length. /// Address must be a valid hex string of the correct length.
async fn process_get_account_data(&self, request: Request) -> Result<Value, RpcErr> { async fn process_get_account(&self, request: Request) -> Result<Value, RpcErr> {
let get_account_nonces_req = GetAccountDataRequest::parse(Some(request.params))?; let get_account_nonces_req = GetAccountRequest::parse(Some(request.params))?;
let address = get_account_nonces_req let address = get_account_nonces_req
.address .address
@ -220,12 +220,7 @@ impl JsonHandler {
state.store.state.get_account_by_address(&address) state.store.state.get_account_by_address(&address)
}; };
let helperstruct = GetAccountDataResponse { let helperstruct = GetAccountResponse { account };
balance: account.balance,
nonce: account.nonce,
program_owner: account.program_owner,
data: account.data,
};
respond(helperstruct) respond(helperstruct)
} }
@ -265,7 +260,7 @@ impl JsonHandler {
GET_INITIAL_TESTNET_ACCOUNTS => self.get_initial_testnet_accounts(request).await, GET_INITIAL_TESTNET_ACCOUNTS => self.get_initial_testnet_accounts(request).await,
GET_ACCOUNT_BALANCE => self.process_get_account_balance(request).await, GET_ACCOUNT_BALANCE => self.process_get_account_balance(request).await,
GET_ACCOUNTS_NONCES => self.process_get_accounts_nonces(request).await, GET_ACCOUNTS_NONCES => self.process_get_accounts_nonces(request).await,
GET_ACCOUNT_DATA => self.process_get_account_data(request).await, GET_ACCOUNT => self.process_get_account(request).await,
GET_TRANSACTION_BY_HASH => self.process_get_transaction_by_hash(request).await, GET_TRANSACTION_BY_HASH => self.process_get_transaction_by_hash(request).await,
_ => Err(RpcErr(RpcError::method_not_found(request.method))), _ => Err(RpcErr(RpcError::method_not_found(request.method))),
} }
@ -278,7 +273,10 @@ mod tests {
use crate::{JsonHandler, rpc_handler}; use crate::{JsonHandler, rpc_handler};
use base64::{Engine, engine::general_purpose}; use base64::{Engine, engine::general_purpose};
use common::rpc_primitives::RpcPollingConfig; use common::{
rpc_primitives::RpcPollingConfig, test_utils::sequencer_sign_key_for_testing,
transaction::EncodedTransaction,
};
use sequencer_core::{ use sequencer_core::{
SequencerCore, SequencerCore,
@ -322,14 +320,11 @@ mod tests {
block_create_timeout_millis: 1000, block_create_timeout_millis: 1000,
port: 8080, port: 8080,
initial_accounts, initial_accounts,
signing_key: *sequencer_sign_key_for_testing().value(),
} }
} }
fn components_for_tests() -> ( fn components_for_tests() -> (JsonHandler, Vec<AccountInitialData>, EncodedTransaction) {
JsonHandler,
Vec<AccountInitialData>,
nssa::PublicTransaction,
) {
let config = sequencer_config_for_tests(); let config = sequencer_config_for_tests();
let mut sequencer_core = SequencerCore::start_from_config(config); let mut sequencer_core = SequencerCore::start_from_config(config);
let initial_accounts = sequencer_core.sequencer_config.initial_accounts.clone(); let initial_accounts = sequencer_core.sequencer_config.initial_accounts.clone();
@ -534,7 +529,7 @@ mod tests {
let (json_handler, _, _) = components_for_tests(); let (json_handler, _, _) = components_for_tests();
let request = serde_json::json!({ let request = serde_json::json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "get_account_data", "method": "get_account",
"params": { "address": "efac".repeat(16) }, "params": { "address": "efac".repeat(16) },
"id": 1 "id": 1
}); });
@ -542,10 +537,12 @@ mod tests {
"id": 1, "id": 1,
"jsonrpc": "2.0", "jsonrpc": "2.0",
"result": { "result": {
"balance": 0, "account": {
"nonce": 0, "balance": 0,
"program_owner": [ 0, 0, 0, 0, 0, 0, 0, 0], "nonce": 0,
"data": [], "program_owner": [ 0, 0, 0, 0, 0, 0, 0, 0],
"data": [],
}
} }
}); });

View File

@ -7,6 +7,13 @@
"block_create_timeout_millis": 10000, "block_create_timeout_millis": 10000,
"port": 3040, "port": 3040,
"initial_accounts": [ "initial_accounts": [
{
"addr": "1b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f",
"balance": 10000
},
{
"addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
"balance": 20000
}
] ]
} }

View File

@ -77,7 +77,9 @@ pub async fn main_runner() -> Result<()> {
} }
//ToDo: Add restart on failures //ToDo: Add restart on failures
let (_, _) = startup_sequencer(app_config).await?; let (_, main_loop_handle) = startup_sequencer(app_config).await?;
main_loop_handle.await??;
Ok(()) Ok(())
} }

View File

@ -92,7 +92,7 @@ impl RocksDBIO {
if is_start_set { if is_start_set {
Ok(dbio) Ok(dbio)
} else if let Some(block) = start_block { } else if let Some(block) = start_block {
let block_id = block.block_id; let block_id = block.header.block_id;
dbio.put_meta_first_block_in_db(block)?; dbio.put_meta_first_block_in_db(block)?;
dbio.put_meta_is_first_block_set()?; dbio.put_meta_is_first_block_set()?;
@ -186,7 +186,7 @@ impl RocksDBIO {
.put_cf( .put_cf(
&cf_meta, &cf_meta,
DB_META_FIRST_BLOCK_IN_DB_KEY.as_bytes(), DB_META_FIRST_BLOCK_IN_DB_KEY.as_bytes(),
block.block_id.to_be_bytes(), block.header.block_id.to_be_bytes(),
) )
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
@ -233,22 +233,22 @@ impl RocksDBIO {
if !first { if !first {
let last_curr_block = self.get_meta_last_block_in_db()?; let last_curr_block = self.get_meta_last_block_in_db()?;
if block.block_id > last_curr_block { if block.header.block_id > last_curr_block {
self.put_meta_last_block_in_db(block.block_id)?; self.put_meta_last_block_in_db(block.header.block_id)?;
} }
} }
self.db self.db
.put_cf( .put_cf(
&cf_block, &cf_block,
block.block_id.to_be_bytes(), block.header.block_id.to_be_bytes(),
HashableBlockData::from(block).to_bytes(), HashableBlockData::from(block).to_bytes(),
) )
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
Ok(()) Ok(())
} }
pub fn get_block(&self, block_id: u64) -> DbResult<Block> { pub fn get_block(&self, block_id: u64) -> DbResult<HashableBlockData> {
let cf_block = self.block_column(); let cf_block = self.block_column();
let res = self let res = self
.db .db
@ -256,7 +256,7 @@ impl RocksDBIO {
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?; .map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
if let Some(data) = res { if let Some(data) = res {
Ok(HashableBlockData::from_bytes(&data).into()) Ok(HashableBlockData::from_bytes(&data))
} else { } else {
Err(DbError::db_interaction_error( Err(DbError::db_interaction_error(
"Block on this id not found".to_string(), "Block on this id not found".to_string(),

View File

@ -14,6 +14,7 @@ tempfile.workspace = true
clap.workspace = true clap.workspace = true
nssa-core = { path = "../nssa/core" } nssa-core = { path = "../nssa/core" }
base64.workspace = true base64.workspace = true
bytemuck = "1.23.2"
[dependencies.key_protocol] [dependencies.key_protocol]
path = "../key_protocol" path = "../key_protocol"

View File

@ -1,14 +1,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use anyhow::Result; use anyhow::Result;
use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree;
use key_protocol::key_protocol_core::NSSAUserData; use key_protocol::key_protocol_core::NSSAUserData;
use crate::config::{PersistentAccountData, WalletConfig}; use crate::config::{PersistentAccountData, WalletConfig};
pub struct WalletChainStore { pub struct WalletChainStore {
pub user_data: NSSAUserData, pub user_data: NSSAUserData,
pub utxo_commitments_store: UTXOCommitmentsMerkleTree,
pub wallet_config: WalletConfig, pub wallet_config: WalletConfig,
} }
@ -21,11 +19,8 @@ impl WalletChainStore {
.map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key)) .map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key))
.collect(); .collect();
let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]);
Ok(Self { Ok(Self {
user_data: NSSAUserData::new_with_accounts(accounts_keys)?, user_data: NSSAUserData::new_with_accounts(accounts_keys)?,
utxo_commitments_store,
wallet_config: config, wallet_config: config,
}) })
} }
@ -94,11 +89,6 @@ mod tests {
let config = create_sample_wallet_config(path.to_path_buf()); let config = create_sample_wallet_config(path.to_path_buf());
let store = WalletChainStore::new(config.clone()).unwrap(); let _ = WalletChainStore::new(config.clone()).unwrap();
assert_eq!(
store.utxo_commitments_store.get_root().unwrap_or([0; 32]),
[0; 32]
);
} }
} }

View File

@ -1,20 +1,22 @@
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr}; use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr};
use anyhow::Result; use anyhow::Result;
use key_protocol::key_protocol_core::NSSAUserData; use key_protocol::key_protocol_core::NSSAUserData;
use nssa::Address; use nssa::{Account, Address};
use serde::Serialize;
use crate::{ use crate::{
HOME_DIR_ENV_VAR, HOME_DIR_ENV_VAR,
config::{PersistentAccountData, WalletConfig}, config::{PersistentAccountData, WalletConfig},
}; };
///Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed. /// Get home dir for wallet. Env var `NSSA_WALLET_HOME_DIR` must be set before execution to succeed.
pub fn get_home() -> Result<PathBuf> { pub fn get_home() -> Result<PathBuf> {
Ok(PathBuf::from_str(&std::env::var(HOME_DIR_ENV_VAR)?)?) Ok(PathBuf::from_str(&std::env::var(HOME_DIR_ENV_VAR)?)?)
} }
///Fetch config from `NSSA_WALLET_HOME_DIR` /// Fetch config from `NSSA_WALLET_HOME_DIR`
pub fn fetch_config() -> Result<WalletConfig> { pub fn fetch_config() -> Result<WalletConfig> {
let config_home = get_home()?; let config_home = get_home()?;
let file = File::open(config_home.join("wallet_config.json"))?; let file = File::open(config_home.join("wallet_config.json"))?;
@ -23,12 +25,12 @@ pub fn fetch_config() -> Result<WalletConfig> {
Ok(serde_json::from_reader(reader)?) Ok(serde_json::from_reader(reader)?)
} }
//ToDo: Replace with structures conversion in future // ToDo: Replace with structures conversion in future
pub fn produce_account_addr_from_hex(hex_str: String) -> Result<Address> { pub fn produce_account_addr_from_hex(hex_str: String) -> Result<Address> {
Ok(hex_str.parse()?) Ok(hex_str.parse()?)
} }
///Fetch list of accounts stored at `NSSA_WALLET_HOME_DIR/curr_accounts.json` /// Fetch list of accounts stored at `NSSA_WALLET_HOME_DIR/curr_accounts.json`
/// ///
/// If file not present, it is considered as empty list of persistent accounts /// If file not present, it is considered as empty list of persistent accounts
pub fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> { pub fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> {
@ -49,7 +51,7 @@ pub fn fetch_persistent_accounts() -> Result<Vec<PersistentAccountData>> {
} }
} }
///Produces a list of accounts for storage /// Produces a list of accounts for storage
pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccountData> { pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccountData> {
let mut vec_for_storage = vec![]; let mut vec_for_storage = vec![];
@ -63,6 +65,28 @@ pub fn produce_data_for_storage(user_data: &NSSAUserData) -> Vec<PersistentAccou
vec_for_storage vec_for_storage
} }
/// Human-readable representation of an account.
#[derive(Serialize)]
pub(crate) struct HumanReadableAccount {
balance: u128,
program_owner_b64: String,
data_b64: String,
nonce: u128,
}
impl From<Account> for HumanReadableAccount {
fn from(account: Account) -> Self {
let program_owner_b64 = BASE64.encode(bytemuck::cast_slice(&account.program_owner));
let data_b64 = BASE64.encode(account.data);
Self {
balance: account.balance,
program_owner_b64,
data_b64,
nonce: account.nonce,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,9 +1,10 @@
use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc}; use std::{fs::File, io::Write, path::PathBuf, str::FromStr, sync::Arc};
use base64::Engine; use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
use common::{ use common::{
ExecutionFailureKind, ExecutionFailureKind,
sequencer_client::{SequencerClient, json::SendTxResponse}, sequencer_client::{SequencerClient, json::SendTxResponse},
transaction::{EncodedTransaction, NSSATransaction},
}; };
use anyhow::Result; use anyhow::Result;
@ -17,8 +18,8 @@ use nssa_core::account::Account;
use crate::{ use crate::{
helperfunctions::{ helperfunctions::{
fetch_config, fetch_persistent_accounts, get_home, produce_account_addr_from_hex, HumanReadableAccount, fetch_config, fetch_persistent_accounts, get_home,
produce_data_for_storage, produce_account_addr_from_hex, produce_data_for_storage,
}, },
poller::TxPoller, poller::TxPoller,
}; };
@ -99,7 +100,7 @@ impl WalletCore {
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
let tx = nssa::PublicTransaction::new(message, witness_set); let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_tx(tx).await?) Ok(self.sequencer_client.send_tx_public(tx).await?)
} }
pub async fn send_public_native_token_transfer( pub async fn send_public_native_token_transfer(
@ -138,7 +139,7 @@ impl WalletCore {
let tx = nssa::PublicTransaction::new(message, witness_set); let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_tx(tx).await?) Ok(self.sequencer_client.send_tx_public(tx).await?)
} else { } else {
Err(ExecutionFailureKind::InsufficientFundsError) Err(ExecutionFailureKind::InsufficientFundsError)
} }
@ -162,17 +163,19 @@ impl WalletCore {
.nonces) .nonces)
} }
///Poll transactions ///Get account
pub async fn poll_public_native_token_transfer( pub async fn get_account(&self, addr: Address) -> Result<Account> {
&self, let response = self.sequencer_client.get_account(addr.to_string()).await?;
hash: String, Ok(response.account)
) -> Result<nssa::PublicTransaction> { }
let transaction_encoded = self.poller.poll_tx(hash).await?;
let tx_base64_decode =
base64::engine::general_purpose::STANDARD.decode(transaction_encoded)?;
let pub_tx = nssa::PublicTransaction::from_bytes(&tx_base64_decode)?;
Ok(pub_tx) ///Poll transactions
pub async fn poll_public_native_token_transfer(&self, hash: String) -> Result<NSSATransaction> {
let transaction_encoded = self.poller.poll_tx(hash).await?;
let tx_base64_decode = BASE64.decode(transaction_encoded)?;
let pub_tx = EncodedTransaction::from_bytes(tx_base64_decode);
Ok(NSSATransaction::try_from(&pub_tx)?)
} }
} }
@ -209,7 +212,11 @@ pub enum Command {
#[arg(short, long)] #[arg(short, long)]
addr: String, addr: String,
}, },
///Get account at address `addr`
GetAccount {
#[arg(short, long)]
addr: String,
},
// TODO: Testnet only. Refactor to prevent compilation on mainnet. // TODO: Testnet only. Refactor to prevent compilation on mainnet.
// Claim piñata prize // Claim piñata prize
ClaimPinata { ClaimPinata {
@ -247,21 +254,19 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
.send_public_native_token_transfer(from, to, amount) .send_public_native_token_transfer(from, to, amount)
.await?; .await?;
info!("Results of tx send is {res:#?}"); println!("Results of tx send is {res:#?}");
let transfer_tx = wallet_core let transfer_tx = wallet_core
.poll_public_native_token_transfer(res.tx_hash) .poll_public_native_token_transfer(res.tx_hash)
.await?; .await?;
info!("Transaction data is {transfer_tx:?}"); println!("Transaction data is {transfer_tx:?}");
} }
Command::RegisterAccount {} => { Command::RegisterAccount {} => {
let addr = wallet_core.create_new_account(); let addr = wallet_core.create_new_account();
wallet_core.store_persistent_accounts()?;
let key = wallet_core.storage.user_data.get_account_signing_key(&addr);
println!("Generated new account with addr {addr}"); println!("Generated new account with addr {addr}");
info!("With key {key:#?}");
} }
Command::FetchTx { tx_hash } => { Command::FetchTx { tx_hash } => {
let tx_obj = wallet_core let tx_obj = wallet_core
@ -269,7 +274,7 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
.get_transaction_by_hash(tx_hash) .get_transaction_by_hash(tx_hash)
.await?; .await?;
info!("Transaction object {tx_obj:#?}"); println!("Transaction object {tx_obj:#?}");
} }
Command::GetAccountBalance { addr } => { Command::GetAccountBalance { addr } => {
let addr = Address::from_str(&addr)?; let addr = Address::from_str(&addr)?;
@ -283,6 +288,11 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0]; let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0];
println!("Accounts {addr} nonce is {nonce}"); println!("Accounts {addr} nonce is {nonce}");
} }
Command::GetAccount { addr } => {
let addr: Address = addr.parse()?;
let account: HumanReadableAccount = wallet_core.get_account(addr).await?.into();
println!("{}", serde_json::to_string(&account).unwrap());
}
Command::ClaimPinata { Command::ClaimPinata {
pinata_addr, pinata_addr,
winner_addr, winner_addr,
@ -299,7 +309,5 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
} }
} }
wallet_core.store_persistent_accounts()?;
Ok(()) Ok(())
} }