mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-02 13:23:10 +00:00
Merge branch 'Pravdyvy/sequencer-update' into Pravdyvy/wallet-privacy-preserving-transactions
This commit is contained in:
commit
6552e6d015
@ -41,6 +41,7 @@ ark-bn254 = "0.5.0"
|
||||
ark-ff = "0.5.0"
|
||||
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
|
||||
base64 = "0.22.1"
|
||||
chrono = "0.4.41"
|
||||
|
||||
rocksdb = { version = "0.21.0", default-features = false, features = [
|
||||
"snappy",
|
||||
|
||||
@ -1,41 +1,56 @@
|
||||
use rs_merkle::Hasher;
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
use rs_merkle::Hasher;
|
||||
|
||||
use crate::merkle_tree_public::hasher::OwnHasher;
|
||||
use nssa;
|
||||
use crate::{OwnHasher, transaction::EncodedTransaction};
|
||||
|
||||
pub type BlockHash = [u8; 32];
|
||||
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)]
|
||||
pub struct Block {
|
||||
pub block_id: BlockId,
|
||||
pub prev_block_id: BlockId,
|
||||
pub prev_block_hash: BlockHash,
|
||||
pub hash: BlockHash,
|
||||
pub transactions: Vec<nssa::PublicTransaction>,
|
||||
pub header: BlockHeader,
|
||||
pub body: BlockBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct HashableBlockData {
|
||||
pub block_id: BlockId,
|
||||
pub prev_block_id: BlockId,
|
||||
pub prev_block_hash: BlockHash,
|
||||
pub transactions: Vec<nssa::PublicTransaction>,
|
||||
pub timestamp: TimeStamp,
|
||||
pub transactions: Vec<EncodedTransaction>,
|
||||
}
|
||||
|
||||
impl From<HashableBlockData> for Block {
|
||||
fn from(value: HashableBlockData) -> Self {
|
||||
let data = value.to_bytes();
|
||||
let hash = OwnHasher::hash(&data);
|
||||
|
||||
Self {
|
||||
block_id: value.block_id,
|
||||
prev_block_id: value.prev_block_id,
|
||||
hash,
|
||||
transactions: value.transactions,
|
||||
prev_block_hash: value.prev_block_hash,
|
||||
impl HashableBlockData {
|
||||
pub fn into_block(self, signing_key: &nssa::PrivateKey) -> Block {
|
||||
let data_bytes = self.to_bytes();
|
||||
let signature = nssa::Signature::new(signing_key, &data_bytes);
|
||||
let hash = OwnHasher::hash(&data_bytes);
|
||||
Block {
|
||||
header: BlockHeader {
|
||||
block_id: self.block_id,
|
||||
prev_block_hash: self.prev_block_hash,
|
||||
hash,
|
||||
timestamp: self.timestamp,
|
||||
signature,
|
||||
},
|
||||
body: BlockBody {
|
||||
transactions: self.transactions,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,10 +58,10 @@ impl From<HashableBlockData> for Block {
|
||||
impl From<Block> for HashableBlockData {
|
||||
fn from(value: Block) -> Self {
|
||||
Self {
|
||||
block_id: value.block_id,
|
||||
prev_block_id: value.prev_block_id,
|
||||
prev_block_hash: value.prev_block_hash,
|
||||
transactions: value.transactions,
|
||||
block_id: value.header.block_id,
|
||||
prev_block_hash: value.header.prev_block_hash,
|
||||
timestamp: value.header.timestamp,
|
||||
transactions: value.body.transactions,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,11 +70,15 @@ impl HashableBlockData {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = Vec::new();
|
||||
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.timestamp.to_le_bytes());
|
||||
let num_transactions: u32 = self.transactions.len() as u32;
|
||||
bytes.extend_from_slice(&num_transactions.to_le_bytes());
|
||||
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
|
||||
@ -70,37 +89,47 @@ impl HashableBlockData {
|
||||
let mut cursor = Cursor::new(data);
|
||||
|
||||
let block_id = u64_from_cursor(&mut cursor);
|
||||
let prev_block_id = u64_from_cursor(&mut cursor);
|
||||
|
||||
let mut prev_block_hash = [0u8; 32];
|
||||
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 mut transactions = Vec::with_capacity(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);
|
||||
}
|
||||
|
||||
Self {
|
||||
block_id,
|
||||
prev_block_id,
|
||||
prev_block_hash,
|
||||
timestamp,
|
||||
transactions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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];
|
||||
cursor.read_exact(&mut word_buf).unwrap();
|
||||
u32::from_le_bytes(word_buf)
|
||||
}
|
||||
|
||||
// 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];
|
||||
cursor.read_exact(&mut word_buf).unwrap();
|
||||
u64::from_le_bytes(word_buf)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::merkle_tree_public::CommitmentHashType;
|
||||
use crate::CommitmentHashType;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
|
||||
pub struct Commitment {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::merkle_tree_public::TreeHashType;
|
||||
use crate::TreeHashType;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PublicNativeTokenSend {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use merkle_tree_public::TreeHashType;
|
||||
use rs_merkle::Hasher;
|
||||
use serde::Deserialize;
|
||||
use sha2::{Digest, Sha256, digest::FixedOutput};
|
||||
|
||||
pub mod block;
|
||||
pub mod commitment;
|
||||
pub mod execution_input;
|
||||
pub mod merkle_tree_public;
|
||||
pub mod nullifier;
|
||||
pub mod rpc_primitives;
|
||||
pub mod sequencer_client;
|
||||
@ -16,6 +16,25 @@ pub mod test_utils;
|
||||
|
||||
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
|
||||
pub type AccountId = TreeHashType;
|
||||
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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>;
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::merkle_tree_public::TreeHashType;
|
||||
use crate::TreeHashType;
|
||||
|
||||
//ToDo: Update Nullifier model, when it is clear
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
|
||||
@ -12,6 +12,7 @@ use crate::rpc_primitives::requests::{
|
||||
GetTransactionByHashResponse,
|
||||
};
|
||||
use crate::sequencer_client::json::AccountInitialData;
|
||||
use crate::transaction::{EncodedTransaction, NSSATransaction};
|
||||
use crate::{SequencerClientError, SequencerRpcError};
|
||||
|
||||
pub mod json;
|
||||
@ -127,10 +128,12 @@ impl SequencerClient {
|
||||
}
|
||||
|
||||
///Send transaction to sequencer
|
||||
pub async fn send_tx(
|
||||
pub async fn send_tx_public(
|
||||
&self,
|
||||
transaction: nssa::PublicTransaction,
|
||||
) -> Result<SendTxResponse, SequencerClientError> {
|
||||
let transaction = EncodedTransaction::from(NSSATransaction::Public(transaction));
|
||||
|
||||
let tx_req = SendTxRequest {
|
||||
transaction: transaction.to_bytes(),
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -10,23 +17,23 @@ use crate::block::{Block, HashableBlockData};
|
||||
///
|
||||
/// `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(
|
||||
id: u64,
|
||||
prev_hash: Option<[u8; 32]>,
|
||||
transactions: Vec<nssa::PublicTransaction>,
|
||||
transactions: Vec<EncodedTransaction>,
|
||||
) -> Block {
|
||||
let block_data = HashableBlockData {
|
||||
block_id: id,
|
||||
prev_block_id: id.saturating_sub(1),
|
||||
prev_block_hash: prev_hash.unwrap_or_default(),
|
||||
timestamp: id * 100,
|
||||
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 addresses = vec![];
|
||||
let nonces = vec![];
|
||||
@ -36,7 +43,10 @@ pub fn produce_dummy_empty_transaction() -> nssa::PublicTransaction {
|
||||
.unwrap();
|
||||
let private_key = nssa::PrivateKey::try_new([1; 32]).unwrap();
|
||||
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(
|
||||
@ -45,7 +55,7 @@ pub fn create_transaction_native_token_transfer(
|
||||
to: [u8; 32],
|
||||
balance_to_move: u128,
|
||||
signing_key: nssa::PrivateKey,
|
||||
) -> nssa::PublicTransaction {
|
||||
) -> EncodedTransaction {
|
||||
let addresses = vec![nssa::Address::new(from), nssa::Address::new(to)];
|
||||
let nonces = vec![nonce];
|
||||
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)
|
||||
.unwrap();
|
||||
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))
|
||||
}
|
||||
|
||||
@ -1,22 +1,34 @@
|
||||
use k256::ecdsa::{
|
||||
Signature, SigningKey, VerifyingKey,
|
||||
signature::{Signer, Verifier},
|
||||
};
|
||||
use k256::ecdsa::{Signature, SigningKey, VerifyingKey};
|
||||
use log::info;
|
||||
use secp256k1_zkp::{PedersenCommitment, Tweak};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use sha2::{Digest, digest::FixedOutput};
|
||||
|
||||
use crate::merkle_tree_public::TreeHashType;
|
||||
|
||||
use elliptic_curve::{
|
||||
consts::{B0, B1},
|
||||
generic_array::GenericArray,
|
||||
};
|
||||
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 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)]
|
||||
pub enum TxKind {
|
||||
Public,
|
||||
Private,
|
||||
Shielded,
|
||||
Deshielded,
|
||||
PrivacyPreserving,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
///General transaction object
|
||||
pub struct TransactionBody {
|
||||
pub struct EncodedTransaction {
|
||||
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
|
||||
pub encoded_data: Vec<(CipherText, Vec<u8>, Tag)>,
|
||||
///Transaction senders ephemeral pub key
|
||||
pub ephemeral_pub_key: Vec<u8>,
|
||||
///Public (Pedersen) commitment
|
||||
pub commitment: Vec<PedersenCommitment>,
|
||||
///tweak
|
||||
pub tweak: Tweak,
|
||||
///secret_r
|
||||
pub secret_r: [u8; 32],
|
||||
///Hex-encoded address of a smart contract account called
|
||||
pub sc_addr: String,
|
||||
pub encoded_transaction_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<NSSATransaction> for EncodedTransaction {
|
||||
fn from(value: NSSATransaction) -> Self {
|
||||
match value {
|
||||
NSSATransaction::Public(tx) => Self {
|
||||
tx_kind: TxKind::Public,
|
||||
encoded_transaction_data: tx.to_bytes(),
|
||||
},
|
||||
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)]
|
||||
@ -153,7 +171,7 @@ impl ActionData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionBody {
|
||||
impl EncodedTransaction {
|
||||
/// Computes and returns the SHA-256 hash of the JSON-serialized representation of `self`.
|
||||
pub fn hash(&self) -> TreeHashType {
|
||||
let bytes_to_hash = self.to_bytes();
|
||||
@ -162,180 +180,48 @@ impl TransactionBody {
|
||||
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
|
||||
// that deterministically encodes all transaction fields to bytes
|
||||
// and guarantees serialization will succeed.
|
||||
serde_json::to_vec(&self).unwrap()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: Vec<u8>) -> Self {
|
||||
serde_json::from_slice(&bytes).unwrap()
|
||||
}
|
||||
|
||||
pub fn log(&self) {
|
||||
info!("Transaction hash is {:?}", hex::encode(self.hash()));
|
||||
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 SignaturePublicKey = VerifyingKey;
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use k256::{FieldBytes, ecdsa::signature::Signer};
|
||||
use secp256k1_zkp::{Tweak, constants::SECRET_KEY_SIZE};
|
||||
use sha2::{Digest, digest::FixedOutput};
|
||||
|
||||
use crate::{
|
||||
merkle_tree_public::TreeHashType,
|
||||
transaction::{Transaction, TransactionBody, TxKind},
|
||||
TreeHashType,
|
||||
transaction::{EncodedTransaction, TxKind},
|
||||
};
|
||||
|
||||
fn test_transaction_body() -> TransactionBody {
|
||||
TransactionBody {
|
||||
fn test_transaction_body() -> EncodedTransaction {
|
||||
EncodedTransaction {
|
||||
tx_kind: TxKind::Public,
|
||||
execution_input: 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(),
|
||||
encoded_transaction_data: vec![1, 2, 3, 4],
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
fn test_transaction_hash_is_sha256_of_json_bytes() {
|
||||
let body = test_transaction_body();
|
||||
let expected_hash = {
|
||||
let data = serde_json::to_vec(&body).unwrap();
|
||||
let data = body.to_bytes();
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(&data);
|
||||
TreeHashType::from(hasher.finalize_fixed())
|
||||
@ -347,83 +233,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_constructor() {
|
||||
fn test_to_bytes_from_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.public_key,
|
||||
SignaturePublicKey::from(&private_key)
|
||||
);
|
||||
assert_eq!(transaction.body, body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transaction_body_getter() {
|
||||
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);
|
||||
}
|
||||
let body_bytes = body.to_bytes();
|
||||
let body_new = EncodedTransaction::from_bytes(body_bytes);
|
||||
|
||||
#[test]
|
||||
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);
|
||||
assert_eq!(body, body_new);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::merkle_tree_public::TreeHashType;
|
||||
use crate::TreeHashType;
|
||||
|
||||
//ToDo: Update UTXO Commitment model, when it is clear
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
||||
@ -15,5 +15,7 @@
|
||||
"addr": "4d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766",
|
||||
"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]
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use common::merkle_tree_public::TreeHashType;
|
||||
use common::TreeHashType;
|
||||
use elliptic_curve::PrimeField;
|
||||
use k256::{AffinePoint, FieldBytes, Scalar};
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
|
||||
2
nssa/src/encoding/mod.rs
Normal file
2
nssa/src/encoding/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod privacy_preserving_transaction;
|
||||
pub mod public_transaction;
|
||||
@ -7,16 +7,20 @@ use nssa_core::{
|
||||
};
|
||||
|
||||
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: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/";
|
||||
|
||||
impl EncryptedAccountData {
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = self.ciphertext.to_bytes();
|
||||
bytes.extend_from_slice(&self.epk.to_bytes());
|
||||
bytes.push(self.view_tag);
|
||||
@ -168,3 +172,55 @@ 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))
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
pub mod address;
|
||||
pub mod encoding;
|
||||
pub mod error;
|
||||
mod merkle_tree;
|
||||
mod privacy_preserving_transaction;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
use nssa_core::{
|
||||
MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput,
|
||||
PrivacyPreservingCircuitOutput, SharedSecretKey,
|
||||
@ -12,7 +14,7 @@ use program_methods::{PRIVACY_PRESERVING_CIRCUIT_ELF, PRIVACY_PRESERVING_CIRCUIT
|
||||
|
||||
/// Proof of the privacy preserving execution circuit
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Proof(Vec<u8>);
|
||||
pub struct Proof(pub(super) Vec<u8>);
|
||||
|
||||
impl Proof {
|
||||
pub(crate) fn is_valid_for(&self, circuit_output: &PrivacyPreservingCircuitOutput) -> bool {
|
||||
@ -20,6 +22,35 @@ impl Proof {
|
||||
let receipt = Receipt::new(inner, circuit_output.to_bytes());
|
||||
receipt.verify(PRIVACY_PRESERVING_CIRCUIT_ID).is_ok()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/// Generates a proof of the execution of a NSSA program inside the privacy preserving execution
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
mod encoding;
|
||||
pub mod message;
|
||||
pub mod transaction;
|
||||
pub mod witness_set;
|
||||
|
||||
@ -167,3 +167,42 @@ fn n_unique<T: Eq + Hash>(data: &[T]) -> usize {
|
||||
let set: HashSet<&T> = data.iter().collect();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,8 @@ use crate::{
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WitnessSet {
|
||||
pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
||||
pub(super) proof: Proof,
|
||||
pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
||||
pub(crate) proof: Proof,
|
||||
}
|
||||
|
||||
impl WitnessSet {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
mod encoding;
|
||||
mod message;
|
||||
mod transaction;
|
||||
mod witness_set;
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::{PrivateKey, PublicKey, Signature, public_transaction::Message};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WitnessSet {
|
||||
pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
||||
pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
|
||||
}
|
||||
|
||||
impl WitnessSet {
|
||||
|
||||
@ -9,6 +9,7 @@ anyhow.workspace = true
|
||||
serde.workspace = true
|
||||
rand.workspace = true
|
||||
tempfile.workspace = true
|
||||
chrono.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
[dependencies.storage]
|
||||
|
||||
@ -27,4 +27,6 @@ pub struct SequencerConfig {
|
||||
pub port: u16,
|
||||
///List of initial accounts data
|
||||
pub initial_accounts: Vec<AccountInitialData>,
|
||||
///Sequencer own signing key
|
||||
pub signing_key: [u8; 32],
|
||||
}
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use anyhow::Result;
|
||||
use common::{block::HashableBlockData, merkle_tree_public::TreeHashType};
|
||||
use common::{
|
||||
TreeHashType,
|
||||
block::HashableBlockData,
|
||||
transaction::{EncodedTransaction, NSSATransaction},
|
||||
};
|
||||
use config::SequencerConfig;
|
||||
use log::warn;
|
||||
use mempool::MemPool;
|
||||
@ -13,7 +17,7 @@ pub mod sequencer_store;
|
||||
|
||||
pub struct SequencerCore {
|
||||
pub store: SequecerChainStore,
|
||||
pub mempool: MemPool<nssa::PublicTransaction>,
|
||||
pub mempool: MemPool<EncodedTransaction>,
|
||||
pub sequencer_config: SequencerConfig,
|
||||
pub chain_height: u64,
|
||||
}
|
||||
@ -51,6 +55,7 @@ impl SequencerCore {
|
||||
config.genesis_id,
|
||||
config.is_genesis_random,
|
||||
&config.initial_accounts,
|
||||
nssa::PrivateKey::try_new(config.signing_key).unwrap(),
|
||||
),
|
||||
mempool: MemPool::default(),
|
||||
chain_height: config.genesis_id,
|
||||
@ -60,20 +65,37 @@ impl SequencerCore {
|
||||
|
||||
pub fn transaction_pre_check(
|
||||
&mut self,
|
||||
tx: nssa::PublicTransaction,
|
||||
) -> Result<nssa::PublicTransaction, TransactionMalformationErrorKind> {
|
||||
tx: NSSATransaction,
|
||||
) -> Result<NSSATransaction, TransactionMalformationErrorKind> {
|
||||
// Stateless checks here
|
||||
if tx.witness_set().is_valid_for(tx.message()) {
|
||||
Ok(tx)
|
||||
} else {
|
||||
Err(TransactionMalformationErrorKind::InvalidSignature)
|
||||
match tx {
|
||||
NSSATransaction::Public(tx) => {
|
||||
if tx.witness_set().is_valid_for(tx.message()) {
|
||||
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(
|
||||
&mut self,
|
||||
transaction: nssa::PublicTransaction,
|
||||
transaction: EncodedTransaction,
|
||||
) -> Result<(), TransactionMalformationErrorKind> {
|
||||
let transaction = NSSATransaction::try_from(&transaction).map_err(|_| {
|
||||
TransactionMalformationErrorKind::FailedToDecode {
|
||||
tx: transaction.hash(),
|
||||
}
|
||||
})?;
|
||||
|
||||
let mempool_size = self.mempool.len();
|
||||
if mempool_size >= self.sequencer_config.max_num_tx_in_block {
|
||||
return Err(TransactionMalformationErrorKind::MempoolFullForRound);
|
||||
@ -83,19 +105,29 @@ impl SequencerCore {
|
||||
.transaction_pre_check(transaction)
|
||||
.inspect_err(|err| warn!("Error at pre_check {err:#?}"))?;
|
||||
|
||||
self.mempool.push_item(authenticated_tx);
|
||||
self.mempool.push_item(authenticated_tx.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_check_transaction_on_state(
|
||||
&mut self,
|
||||
tx: nssa::PublicTransaction,
|
||||
) -> Result<nssa::PublicTransaction, nssa::error::NssaError> {
|
||||
self.store
|
||||
.state
|
||||
.transition_from_public_transaction(&tx)
|
||||
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
||||
tx: NSSATransaction,
|
||||
) -> Result<NSSATransaction, nssa::error::NssaError> {
|
||||
match &tx {
|
||||
NSSATransaction::Public(tx) => {
|
||||
self.store
|
||||
.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)
|
||||
}
|
||||
@ -104,29 +136,41 @@ impl SequencerCore {
|
||||
pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result<u64> {
|
||||
let new_block_height = self.chain_height + 1;
|
||||
|
||||
let transactions = self
|
||||
.mempool
|
||||
.pop_size(self.sequencer_config.max_num_tx_in_block);
|
||||
let mut num_valid_transactions_in_block = 0;
|
||||
let mut valid_transactions = vec![];
|
||||
|
||||
let valid_transactions: Vec<_> = transactions
|
||||
.into_iter()
|
||||
.filter_map(|tx| self.execute_check_transaction_on_state(tx).ok())
|
||||
.collect();
|
||||
while let Some(tx) = self.mempool.pop_last() {
|
||||
let nssa_transaction = NSSATransaction::try_from(&tx)
|
||||
.map_err(|_| TransactionMalformationErrorKind::FailedToDecode { tx: tx.hash() })?;
|
||||
|
||||
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
|
||||
.store
|
||||
.block_store
|
||||
.get_block_at_id(self.chain_height)?
|
||||
.header
|
||||
.hash;
|
||||
|
||||
let curr_time = chrono::Utc::now().timestamp_millis() as u64;
|
||||
|
||||
let hashable_data = HashableBlockData {
|
||||
block_id: new_block_height,
|
||||
prev_block_id: self.chain_height,
|
||||
transactions: valid_transactions,
|
||||
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)?;
|
||||
|
||||
@ -138,10 +182,18 @@ impl SequencerCore {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use common::test_utils::sequencer_sign_key_for_testing;
|
||||
|
||||
use crate::config::AccountInitialData;
|
||||
|
||||
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(
|
||||
initial_accounts: Vec<AccountInitialData>,
|
||||
) -> SequencerConfig {
|
||||
@ -157,6 +209,7 @@ mod tests {
|
||||
block_create_timeout_millis: 1000,
|
||||
port: 8080,
|
||||
initial_accounts,
|
||||
signing_key: *sequencer_sign_key_for_testing().value(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,7 +351,7 @@ mod tests {
|
||||
common_setup(&mut sequencer);
|
||||
|
||||
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());
|
||||
}
|
||||
@ -324,7 +377,7 @@ mod tests {
|
||||
let tx = common::test_utils::create_transaction_native_token_transfer(
|
||||
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());
|
||||
}
|
||||
@ -352,7 +405,9 @@ mod tests {
|
||||
);
|
||||
|
||||
// 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
|
||||
let result = sequencer.execute_check_transaction_on_state(tx);
|
||||
@ -385,7 +440,7 @@ mod tests {
|
||||
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
|
||||
assert!(result.is_ok());
|
||||
@ -421,7 +476,9 @@ mod tests {
|
||||
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
|
||||
.store
|
||||
@ -528,7 +585,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
// Only one should be included in the block
|
||||
assert_eq!(block.transactions, vec![tx.clone()]);
|
||||
assert_eq!(block.body.transactions, vec![tx.clone()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -563,7 +620,7 @@ mod tests {
|
||||
.block_store
|
||||
.get_block_at_id(current_height)
|
||||
.unwrap();
|
||||
assert_eq!(block.transactions, vec![tx.clone()]);
|
||||
assert_eq!(block.body.transactions, vec![tx.clone()]);
|
||||
|
||||
// Add same transaction should fail
|
||||
sequencer.mempool.push_item(tx);
|
||||
@ -575,6 +632,6 @@ mod tests {
|
||||
.block_store
|
||||
.get_block_at_id(current_height)
|
||||
.unwrap();
|
||||
assert!(block.transactions.is_empty());
|
||||
assert!(block.body.transactions.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use anyhow::Result;
|
||||
use common::{block::Block, merkle_tree_public::TreeHashType};
|
||||
use common::{TreeHashType, block::Block, transaction::EncodedTransaction};
|
||||
use storage::RocksDBIO;
|
||||
|
||||
pub struct SequecerBlockStore {
|
||||
@ -9,6 +9,7 @@ pub struct SequecerBlockStore {
|
||||
// TODO: Consider adding the hashmap to the database for faster recovery.
|
||||
tx_hash_to_block_map: HashMap<TreeHashType, u64>,
|
||||
pub genesis_id: u64,
|
||||
pub signing_key: nssa::PrivateKey,
|
||||
}
|
||||
|
||||
impl SequecerBlockStore {
|
||||
@ -16,7 +17,11 @@ impl SequecerBlockStore {
|
||||
/// Creates files if necessary.
|
||||
///
|
||||
/// 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 {
|
||||
block_to_transactions_map(block)
|
||||
} else {
|
||||
@ -31,16 +36,17 @@ impl SequecerBlockStore {
|
||||
dbio,
|
||||
genesis_id,
|
||||
tx_hash_to_block_map,
|
||||
signing_key,
|
||||
})
|
||||
}
|
||||
|
||||
///Reopening existing database
|
||||
pub fn open_db_restart(location: &Path) -> Result<Self> {
|
||||
SequecerBlockStore::open_db_with_genesis(location, None)
|
||||
pub fn open_db_restart(location: &Path, signing_key: nssa::PrivateKey) -> Result<Self> {
|
||||
SequecerBlockStore::open_db_with_genesis(location, None, signing_key)
|
||||
}
|
||||
|
||||
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<()> {
|
||||
@ -51,11 +57,11 @@ impl SequecerBlockStore {
|
||||
}
|
||||
|
||||
/// 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 = block_id.map(|&id| self.get_block_at_id(id));
|
||||
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 {
|
||||
return Some(transaction);
|
||||
}
|
||||
@ -67,9 +73,10 @@ impl SequecerBlockStore {
|
||||
|
||||
fn block_to_transactions_map(block: &Block) -> HashMap<TreeHashType, u64> {
|
||||
block
|
||||
.body
|
||||
.transactions
|
||||
.iter()
|
||||
.map(|transaction| (transaction.hash(), block.block_id))
|
||||
.map(|transaction| (transaction.hash(), block.header.block_id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -77,22 +84,28 @@ fn block_to_transactions_map(block: &Block) -> HashMap<TreeHashType, u64> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use common::{block::HashableBlockData, test_utils::sequencer_sign_key_for_testing};
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn test_get_transaction_by_hash() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
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,
|
||||
prev_block_id: 0,
|
||||
prev_block_hash: [0; 32],
|
||||
hash: [1; 32],
|
||||
timestamp: 0,
|
||||
transactions: vec![],
|
||||
};
|
||||
|
||||
let genesis_block = genesis_block_hashable_data.into_block(&signing_key);
|
||||
// Start an empty 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 block = common::test_utils::produce_dummy_block(1, None, vec![tx.clone()]);
|
||||
|
||||
@ -20,6 +20,7 @@ impl SequecerChainStore {
|
||||
genesis_id: u64,
|
||||
is_genesis_random: bool,
|
||||
initial_accounts: &[AccountInitialData],
|
||||
signing_key: nssa::PrivateKey,
|
||||
) -> Self {
|
||||
let init_accs: Vec<(Address, u128)> = initial_accounts
|
||||
.iter()
|
||||
@ -36,20 +37,23 @@ impl SequecerChainStore {
|
||||
OsRng.fill_bytes(&mut prev_block_hash);
|
||||
}
|
||||
|
||||
let curr_time = chrono::Utc::now().timestamp_millis() as u64;
|
||||
|
||||
let hashable_data = HashableBlockData {
|
||||
block_id: genesis_id,
|
||||
prev_block_id: genesis_id.saturating_sub(1),
|
||||
transactions: vec![],
|
||||
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,
|
||||
//as fixing this issue may require actions non-native to program scope
|
||||
let block_store = SequecerBlockStore::open_db_with_genesis(
|
||||
&home_dir.join("rocksdb"),
|
||||
Some(genesis_block),
|
||||
signing_key,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@ -5,8 +5,8 @@ use sequencer_core::config::AccountInitialData;
|
||||
use serde_json::Value;
|
||||
|
||||
use common::{
|
||||
TreeHashType,
|
||||
block::HashableBlockData,
|
||||
merkle_tree_public::TreeHashType,
|
||||
rpc_primitives::{
|
||||
errors::RpcError,
|
||||
message::{Message, Request},
|
||||
@ -18,6 +18,7 @@ use common::{
|
||||
GetTransactionByHashResponse,
|
||||
},
|
||||
},
|
||||
transaction::EncodedTransaction,
|
||||
};
|
||||
|
||||
use common::rpc_primitives::requests::{
|
||||
@ -74,8 +75,7 @@ impl JsonHandler {
|
||||
|
||||
async fn process_send_tx(&self, request: Request) -> Result<Value, RpcErr> {
|
||||
let send_tx_req = SendTxRequest::parse(Some(request.params))?;
|
||||
let tx = nssa::PublicTransaction::from_bytes(&send_tx_req.transaction)
|
||||
.map_err(|e| RpcError::serialization_error(&e.to_string()))?;
|
||||
let tx = EncodedTransaction::from_bytes(send_tx_req.transaction);
|
||||
let tx_hash = hex::encode(tx.hash());
|
||||
|
||||
{
|
||||
@ -278,7 +278,10 @@ mod tests {
|
||||
|
||||
use crate::{JsonHandler, rpc_handler};
|
||||
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::{
|
||||
SequencerCore,
|
||||
@ -322,14 +325,11 @@ mod tests {
|
||||
block_create_timeout_millis: 1000,
|
||||
port: 8080,
|
||||
initial_accounts,
|
||||
signing_key: *sequencer_sign_key_for_testing().value(),
|
||||
}
|
||||
}
|
||||
|
||||
fn components_for_tests() -> (
|
||||
JsonHandler,
|
||||
Vec<AccountInitialData>,
|
||||
nssa::PublicTransaction,
|
||||
) {
|
||||
fn components_for_tests() -> (JsonHandler, Vec<AccountInitialData>, EncodedTransaction) {
|
||||
let config = sequencer_config_for_tests();
|
||||
let mut sequencer_core = SequencerCore::start_from_config(config);
|
||||
let initial_accounts = sequencer_core.sequencer_config.initial_accounts.clone();
|
||||
|
||||
@ -92,7 +92,7 @@ impl RocksDBIO {
|
||||
if is_start_set {
|
||||
Ok(dbio)
|
||||
} 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_is_first_block_set()?;
|
||||
|
||||
@ -186,7 +186,7 @@ impl RocksDBIO {
|
||||
.put_cf(
|
||||
&cf_meta,
|
||||
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))?;
|
||||
|
||||
@ -233,22 +233,22 @@ impl RocksDBIO {
|
||||
if !first {
|
||||
let last_curr_block = self.get_meta_last_block_in_db()?;
|
||||
|
||||
if block.block_id > last_curr_block {
|
||||
self.put_meta_last_block_in_db(block.block_id)?;
|
||||
if block.header.block_id > last_curr_block {
|
||||
self.put_meta_last_block_in_db(block.header.block_id)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.db
|
||||
.put_cf(
|
||||
&cf_block,
|
||||
block.block_id.to_be_bytes(),
|
||||
block.header.block_id.to_be_bytes(),
|
||||
HashableBlockData::from(block).to_bytes(),
|
||||
)
|
||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||
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 res = self
|
||||
.db
|
||||
@ -256,7 +256,7 @@ impl RocksDBIO {
|
||||
.map_err(|rerr| DbError::rocksdb_cast_message(rerr, None))?;
|
||||
|
||||
if let Some(data) = res {
|
||||
Ok(HashableBlockData::from_bytes(&data).into())
|
||||
Ok(HashableBlockData::from_bytes(&data))
|
||||
} else {
|
||||
Err(DbError::db_interaction_error(
|
||||
"Block on this id not found".to_string(),
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree;
|
||||
use key_protocol::key_protocol_core::NSSAUserData;
|
||||
|
||||
use crate::config::{PersistentAccountData, WalletConfig};
|
||||
|
||||
pub struct WalletChainStore {
|
||||
pub user_data: NSSAUserData,
|
||||
pub utxo_commitments_store: UTXOCommitmentsMerkleTree,
|
||||
pub wallet_config: WalletConfig,
|
||||
}
|
||||
|
||||
@ -21,11 +19,8 @@ impl WalletChainStore {
|
||||
.map(|init_acc_data| (init_acc_data.address, init_acc_data.pub_sign_key))
|
||||
.collect();
|
||||
|
||||
let utxo_commitments_store = UTXOCommitmentsMerkleTree::new(vec![]);
|
||||
|
||||
Ok(Self {
|
||||
user_data: NSSAUserData::new_with_accounts(accounts_keys)?,
|
||||
utxo_commitments_store,
|
||||
wallet_config: config,
|
||||
})
|
||||
}
|
||||
@ -94,11 +89,6 @@ mod tests {
|
||||
|
||||
let config = create_sample_wallet_config(path.to_path_buf());
|
||||
|
||||
let store = WalletChainStore::new(config.clone()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
store.utxo_commitments_store.get_root().unwrap_or([0; 32]),
|
||||
[0; 32]
|
||||
);
|
||||
let _ = WalletChainStore::new(config.clone()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ use base64::Engine;
|
||||
use common::{
|
||||
ExecutionFailureKind,
|
||||
sequencer_client::{SequencerClient, json::SendTxResponse},
|
||||
transaction::{EncodedTransaction, NSSATransaction},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
@ -122,7 +123,7 @@ impl WalletCore {
|
||||
|
||||
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 {
|
||||
Err(ExecutionFailureKind::InsufficientFundsError)
|
||||
}
|
||||
@ -147,16 +148,13 @@ impl WalletCore {
|
||||
}
|
||||
|
||||
///Poll transactions
|
||||
pub async fn poll_public_native_token_transfer(
|
||||
&self,
|
||||
hash: String,
|
||||
) -> Result<nssa::PublicTransaction> {
|
||||
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::engine::general_purpose::STANDARD.decode(transaction_encoded)?;
|
||||
let pub_tx = nssa::PublicTransaction::from_bytes(&tx_base64_decode)?;
|
||||
let pub_tx = EncodedTransaction::from_bytes(tx_base64_decode);
|
||||
|
||||
Ok(pub_tx)
|
||||
Ok(NSSATransaction::try_from(&pub_tx)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user