mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-08 16:23:12 +00:00
add tests. refactor
This commit is contained in:
parent
2d9896f6ed
commit
d3827fc823
@ -65,6 +65,7 @@ impl HashableBlockData {
|
||||
bytes
|
||||
}
|
||||
|
||||
// TODO: Improve error handling. Remove unwraps.
|
||||
pub fn from_bytes(data: &[u8]) -> Self {
|
||||
let mut cursor = Cursor::new(data);
|
||||
|
||||
@ -91,12 +92,14 @@ impl HashableBlockData {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Improve error handling. Remove unwraps.
|
||||
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 {
|
||||
let mut word_buf = [0u8; 8];
|
||||
cursor.read_exact(&mut word_buf).unwrap();
|
||||
@ -108,12 +111,12 @@ mod tests {
|
||||
use crate::{block::HashableBlockData, test_utils};
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
fn test_encoding_roundtrip() {
|
||||
let transactions = vec![test_utils::produce_dummy_empty_transaction()];
|
||||
let block = test_utils::produce_dummy_block(1, Some([1; 32]), transactions);
|
||||
let hashable = HashableBlockData::from(block);
|
||||
let bytes = hashable.to_bytes();
|
||||
let recov = HashableBlockData::from_bytes(&bytes);
|
||||
assert_eq!(hashable, recov);
|
||||
let block_from_bytes = HashableBlockData::from_bytes(&bytes);
|
||||
assert_eq!(hashable, block_from_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,8 +121,8 @@ impl WitnessSet {
|
||||
|
||||
impl PublicTransaction {
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
let mut bytes = self.message.to_bytes();
|
||||
bytes.extend_from_slice(&self.witness_set.to_bytes());
|
||||
let mut bytes = self.message().to_bytes();
|
||||
bytes.extend_from_slice(&self.witness_set().to_bytes());
|
||||
bytes
|
||||
}
|
||||
|
||||
@ -134,10 +134,7 @@ impl PublicTransaction {
|
||||
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(Self {
|
||||
message,
|
||||
witness_set,
|
||||
})
|
||||
Ok(PublicTransaction::new(message, witness_set))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,153 +1,13 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata},
|
||||
program::validate_execution,
|
||||
};
|
||||
use sha2::{Digest, digest::FixedOutput};
|
||||
|
||||
use crate::{V01State, address::Address, error::NssaError};
|
||||
|
||||
mod encoding;
|
||||
mod message;
|
||||
mod witness_set;
|
||||
mod transaction;
|
||||
|
||||
pub use message::Message;
|
||||
pub use witness_set::WitnessSet;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PublicTransaction {
|
||||
message: Message,
|
||||
witness_set: WitnessSet,
|
||||
}
|
||||
|
||||
impl PublicTransaction {
|
||||
pub fn message(&self) -> &Message {
|
||||
&self.message
|
||||
}
|
||||
|
||||
pub fn witness_set(&self) -> &WitnessSet {
|
||||
&self.witness_set
|
||||
}
|
||||
|
||||
pub(crate) fn signer_addresses(&self) -> Vec<Address> {
|
||||
self.witness_set
|
||||
.signatures_and_public_keys
|
||||
.iter()
|
||||
.map(|(_, public_key)| Address::from_public_key(public_key))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn new(message: Message, witness_set: WitnessSet) -> Self {
|
||||
Self {
|
||||
message,
|
||||
witness_set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
let bytes = self.to_bytes();
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(&bytes);
|
||||
hasher.finalize_fixed().into()
|
||||
}
|
||||
|
||||
pub(crate) fn validate_and_compute_post_states(
|
||||
&self,
|
||||
state: &V01State,
|
||||
) -> Result<HashMap<Address, Account>, NssaError> {
|
||||
let message = self.message();
|
||||
let witness_set = self.witness_set();
|
||||
|
||||
// All addresses must be different
|
||||
if message.addresses.iter().collect::<HashSet<_>>().len() != message.addresses.len() {
|
||||
return Err(NssaError::InvalidInput(
|
||||
"Duplicate addresses found in message".into(),
|
||||
));
|
||||
}
|
||||
|
||||
if message.nonces.len() != witness_set.signatures_and_public_keys.len() {
|
||||
return Err(NssaError::InvalidInput(
|
||||
"Mismatch between number of nonces and signatures/public keys".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// Check the signatures are valid
|
||||
if !witness_set.is_valid_for(message) {
|
||||
return Err(NssaError::InvalidInput(
|
||||
"Invalid signature for given message and public key".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let signer_addresses = self.signer_addresses();
|
||||
// Check nonces corresponds to the current nonces on the public state.
|
||||
for (address, nonce) in signer_addresses.iter().zip(&message.nonces) {
|
||||
let current_nonce = state.get_account_by_address(address).nonce;
|
||||
if current_nonce != *nonce {
|
||||
return Err(NssaError::InvalidInput("Nonce mismatch".into()));
|
||||
}
|
||||
}
|
||||
|
||||
// Build pre_states for execution
|
||||
let pre_states: Vec<_> = message
|
||||
.addresses
|
||||
.iter()
|
||||
.map(|address| AccountWithMetadata {
|
||||
account: state.get_account_by_address(address),
|
||||
is_authorized: signer_addresses.contains(address),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Check the `program_id` corresponds to a built-in program
|
||||
// Only allowed program so far is the authenticated transfer program
|
||||
let Some(program) = state.builtin_programs().get(&message.program_id) else {
|
||||
return Err(NssaError::InvalidInput("Unknown program".into()));
|
||||
};
|
||||
|
||||
// // Execute program
|
||||
let post_states = program.execute(&pre_states, &message.instruction_data)?;
|
||||
|
||||
// Verify execution corresponds to a well-behaved program.
|
||||
// See the # Programs section for the definition of the `validate_execution` method.
|
||||
if !validate_execution(&pre_states, &post_states, message.program_id) {
|
||||
return Err(NssaError::InvalidProgramBehavior);
|
||||
}
|
||||
|
||||
Ok(message.addresses.iter().cloned().zip(post_states).collect())
|
||||
}
|
||||
}
|
||||
pub use transaction::PublicTransaction;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Cursor;
|
||||
pub use transaction::tests;
|
||||
|
||||
use crate::{
|
||||
Address, PrivateKey, PublicKey, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_public_transaction_encoding_bytes_roundtrip() {
|
||||
let key1 = PrivateKey::try_new([1; 32]).unwrap();
|
||||
let key2 = PrivateKey::try_new([2; 32]).unwrap();
|
||||
let addr1 = Address::from_public_key(&PublicKey::new(&key1));
|
||||
let addr2 = Address::from_public_key(&PublicKey::new(&key2));
|
||||
let nonces = vec![5, 99];
|
||||
let instruction = 1337;
|
||||
let message = Message::try_new(
|
||||
Program::authenticated_transfer_program().id(),
|
||||
vec![addr1, addr2],
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||
let tx = PublicTransaction::new(message, witness_set);
|
||||
|
||||
let bytes = tx.to_bytes();
|
||||
let tx_from_bytes = PublicTransaction::from_bytes(&bytes).unwrap();
|
||||
assert_eq!(tx, tx_from_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
183
nssa/src/public_transaction/transaction.rs
Normal file
183
nssa/src/public_transaction/transaction.rs
Normal file
@ -0,0 +1,183 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use nssa_core::{
|
||||
account::{Account, AccountWithMetadata},
|
||||
program::validate_execution,
|
||||
};
|
||||
use sha2::{Digest, digest::FixedOutput};
|
||||
|
||||
use crate::{
|
||||
V01State,
|
||||
address::Address,
|
||||
error::NssaError,
|
||||
public_transaction::{Message, WitnessSet},
|
||||
};
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PublicTransaction {
|
||||
message: Message,
|
||||
witness_set: WitnessSet,
|
||||
}
|
||||
|
||||
impl PublicTransaction {
|
||||
pub fn new(message: Message, witness_set: WitnessSet) -> Self {
|
||||
Self {
|
||||
message,
|
||||
witness_set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(&self) -> &Message {
|
||||
&self.message
|
||||
}
|
||||
|
||||
pub fn witness_set(&self) -> &WitnessSet {
|
||||
&self.witness_set
|
||||
}
|
||||
|
||||
pub(crate) fn signer_addresses(&self) -> Vec<Address> {
|
||||
self.witness_set
|
||||
.iter_signatures()
|
||||
.map(|(_, public_key)| Address::from_public_key(public_key))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
let bytes = self.to_bytes();
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(&bytes);
|
||||
hasher.finalize_fixed().into()
|
||||
}
|
||||
|
||||
pub(crate) fn validate_and_compute_post_states(
|
||||
&self,
|
||||
state: &V01State,
|
||||
) -> Result<HashMap<Address, Account>, NssaError> {
|
||||
let message = self.message();
|
||||
let witness_set = self.witness_set();
|
||||
|
||||
// All addresses must be different
|
||||
if message.addresses.iter().collect::<HashSet<_>>().len() != message.addresses.len() {
|
||||
return Err(NssaError::InvalidInput(
|
||||
"Duplicate addresses found in message".into(),
|
||||
));
|
||||
}
|
||||
|
||||
if message.nonces.len() != witness_set.signatures_and_public_keys.len() {
|
||||
return Err(NssaError::InvalidInput(
|
||||
"Mismatch between number of nonces and signatures/public keys".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// Check the signatures are valid
|
||||
if !witness_set.is_valid_for(message) {
|
||||
return Err(NssaError::InvalidInput(
|
||||
"Invalid signature for given message and public key".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let signer_addresses = self.signer_addresses();
|
||||
// Check nonces corresponds to the current nonces on the public state.
|
||||
for (address, nonce) in signer_addresses.iter().zip(&message.nonces) {
|
||||
let current_nonce = state.get_account_by_address(address).nonce;
|
||||
if current_nonce != *nonce {
|
||||
return Err(NssaError::InvalidInput("Nonce mismatch".into()));
|
||||
}
|
||||
}
|
||||
|
||||
// Build pre_states for execution
|
||||
let pre_states: Vec<_> = message
|
||||
.addresses
|
||||
.iter()
|
||||
.map(|address| AccountWithMetadata {
|
||||
account: state.get_account_by_address(address),
|
||||
is_authorized: signer_addresses.contains(address),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Check the `program_id` corresponds to a built-in program
|
||||
// Only allowed program so far is the authenticated transfer program
|
||||
let Some(program) = state.builtin_programs().get(&message.program_id) else {
|
||||
return Err(NssaError::InvalidInput("Unknown program".into()));
|
||||
};
|
||||
|
||||
// // Execute program
|
||||
let post_states = program.execute(&pre_states, &message.instruction_data)?;
|
||||
|
||||
// Verify execution corresponds to a well-behaved program.
|
||||
// See the # Programs section for the definition of the `validate_execution` method.
|
||||
if !validate_execution(&pre_states, &post_states, message.program_id) {
|
||||
return Err(NssaError::InvalidProgramBehavior);
|
||||
}
|
||||
|
||||
Ok(message.addresses.iter().cloned().zip(post_states).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use crate::{
|
||||
Address, PrivateKey, PublicKey, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet, witness_set},
|
||||
};
|
||||
|
||||
pub fn transaction_for_tests() -> PublicTransaction {
|
||||
let key1 = PrivateKey::try_new([1; 32]).unwrap();
|
||||
let key2 = PrivateKey::try_new([2; 32]).unwrap();
|
||||
let addr1 = Address::from_public_key(&PublicKey::new(&key1));
|
||||
let addr2 = Address::from_public_key(&PublicKey::new(&key2));
|
||||
let nonces = vec![5, 99];
|
||||
let instruction = 1337;
|
||||
let message = Message::try_new(
|
||||
Program::authenticated_transfer_program().id(),
|
||||
vec![addr1, addr2],
|
||||
nonces,
|
||||
instruction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]);
|
||||
PublicTransaction::new(message, witness_set)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_constructor() {
|
||||
let tx = transaction_for_tests();
|
||||
let message = tx.message().clone();
|
||||
let witness_set = tx.witness_set().clone();
|
||||
let tx_from_constructor = PublicTransaction::new(message.clone(), witness_set.clone());
|
||||
assert_eq!(tx_from_constructor.message, message);
|
||||
assert_eq!(tx_from_constructor.witness_set, witness_set);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_getter() {
|
||||
let tx = transaction_for_tests();
|
||||
assert_eq!(&tx.message, tx.message());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_witness_set_getter() {
|
||||
let tx = transaction_for_tests();
|
||||
assert_eq!(&tx.witness_set, tx.witness_set());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signer_addresses() {
|
||||
let tx = transaction_for_tests();
|
||||
let expected_signer_addresses = vec![
|
||||
Address::new([
|
||||
27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30,
|
||||
24, 52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143,
|
||||
]),
|
||||
Address::new([
|
||||
77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217,
|
||||
234, 216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102,
|
||||
]),
|
||||
];
|
||||
let signer_addresses = tx.signer_addresses();
|
||||
assert_eq!(signer_addresses, expected_signer_addresses);
|
||||
}
|
||||
}
|
||||
@ -3,3 +3,4 @@ mod program_tests;
|
||||
mod signature_tests;
|
||||
mod state_tests;
|
||||
mod valid_execution_tests;
|
||||
mod public_transaction_tests;
|
||||
|
||||
28
nssa/src/tests/public_transaction_tests.rs
Normal file
28
nssa/src/tests/public_transaction_tests.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use sha2::{Digest, digest::FixedOutput};
|
||||
|
||||
use crate::{
|
||||
Address, PrivateKey, PublicKey, PublicTransaction,
|
||||
program::Program,
|
||||
public_transaction::{Message, WitnessSet, tests::transaction_for_tests},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_public_transaction_encoding_bytes_roundtrip() {
|
||||
let tx = transaction_for_tests();
|
||||
let bytes = tx.to_bytes();
|
||||
let tx_from_bytes = PublicTransaction::from_bytes(&bytes).unwrap();
|
||||
assert_eq!(tx, tx_from_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_is_sha256_of_transaction_bytes() {
|
||||
let tx = transaction_for_tests();
|
||||
let hash = tx.hash();
|
||||
let expected_hash: [u8; 32] = {
|
||||
let bytes = tx.to_bytes();
|
||||
let mut hasher = sha2::Sha256::new();
|
||||
hasher.update(&bytes);
|
||||
hasher.finalize_fixed().into()
|
||||
};
|
||||
assert_eq!(hash, expected_hash);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user