mirror of
https://github.com/logos-blockchain/lez-fuzzing.git
synced 2026-06-07 11:39:30 +00:00
chore: synchronize with latest lee introduction
This commit is contained in:
parent
2adc491361
commit
ccd08aed6f
836
Cargo.lock
generated
836
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@ -52,11 +52,13 @@ unsafe_code = "deny"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
|
||||||
# ── LEZ crates — expects logos-execution-zone/ to be cloned at ../logos-execution-zone ──
|
# ── LEZ crates — expects logos-execution-zone/ to be cloned at ../logos-execution-zone ──
|
||||||
nssa = { path = "../logos-execution-zone/nssa" }
|
# LEZ reorganised its directory layout; the package= key keeps the old dependency
|
||||||
nssa_core = { path = "../logos-execution-zone/nssa/core" }
|
# alias so that fuzz_props source code (use nssa::...) compiles unchanged.
|
||||||
common = { path = "../logos-execution-zone/common" }
|
nssa = { path = "../logos-execution-zone/lee/state_machine", package = "lee" }
|
||||||
key_protocol = { path = "../logos-execution-zone/key_protocol" }
|
nssa_core = { path = "../logos-execution-zone/lee/state_machine/core", package = "lee_core" }
|
||||||
testnet_initial_state = { path = "../logos-execution-zone/testnet_initial_state" }
|
common = { path = "../logos-execution-zone/lez/common" }
|
||||||
|
key_protocol = { path = "../logos-execution-zone/lee/key_protocol" }
|
||||||
|
testnet_initial_state = { path = "../logos-execution-zone/lez/testnet_initial_state" }
|
||||||
token_core = { path = "../logos-execution-zone/programs/token/core" }
|
token_core = { path = "../logos-execution-zone/programs/token/core" }
|
||||||
test_program_methods = { path = "../logos-execution-zone/test_program_methods" }
|
test_program_methods = { path = "../logos-execution-zone/test_program_methods" }
|
||||||
|
|
||||||
|
|||||||
9
Justfile
9
Justfile
@ -788,10 +788,13 @@ mutants-protocol PACKAGES="nssa common":
|
|||||||
|
|
||||||
# ── Housekeeping ──────────────────────────────────────────────────────────────
|
# ── Housekeeping ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# Remove all Cargo build artefacts (workspace + fuzz sub-crate)
|
# Remove all Cargo build artefacts (workspace + fuzz sub-crate + logos-execution-zone)
|
||||||
|
# Each command is prefixed with `-` so that a missing sibling workspace (LEZ not cloned)
|
||||||
|
# does not abort the recipe — cargo clean still removes whatever targets are present.
|
||||||
clean:
|
clean:
|
||||||
cargo clean
|
-cargo clean
|
||||||
cargo clean --manifest-path fuzz/Cargo.toml
|
-cargo clean --manifest-path fuzz/Cargo.toml
|
||||||
|
-cargo clean --manifest-path ../logos-execution-zone/Cargo.toml
|
||||||
|
|
||||||
# Remove libFuzzer crash/timeout artifacts for all targets (corpus is kept)
|
# Remove libFuzzer crash/timeout artifacts for all targets (corpus is kept)
|
||||||
clean-artifacts:
|
clean-artifacts:
|
||||||
|
|||||||
@ -116,7 +116,7 @@ just fuzz-props
|
|||||||
|
|
||||||
| Target | Protocol layer | Entry point |
|
| Target | Protocol layer | Entry point |
|
||||||
|--------|---------------|-------------|
|
|--------|---------------|-------------|
|
||||||
| `fuzz_transaction_decoding` | Borsh decoding of all tx/block types (`NSSATransaction`, `Block`, `HashableBlockData`) with roundtrip re-encoding | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` |
|
| `fuzz_transaction_decoding` | Borsh decoding of all tx/block types (`LeeTransaction`, `Block`, `HashableBlockData`) with roundtrip re-encoding | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` |
|
||||||
| `fuzz_stateless_verification` | `transaction_stateless_check()` no-panic + idempotency | `fuzz/fuzz_targets/fuzz_stateless_verification.rs` |
|
| `fuzz_stateless_verification` | `transaction_stateless_check()` no-panic + idempotency | `fuzz/fuzz_targets/fuzz_stateless_verification.rs` |
|
||||||
| `fuzz_state_transition` | `V03State` transition: StateIsolationOnFailure + BalanceConservation + ReplayRejection invariants across up to 8 txs with fuzz-driven state | `fuzz/fuzz_targets/fuzz_state_transition.rs` |
|
| `fuzz_state_transition` | `V03State` transition: StateIsolationOnFailure + BalanceConservation + ReplayRejection invariants across up to 8 txs with fuzz-driven state | `fuzz/fuzz_targets/fuzz_state_transition.rs` |
|
||||||
| `fuzz_block_verification` | Block hash integrity: HashRoundTrip · HashPreimage completeness (block_id/prev_hash/timestamp) · TxOrderCommitment | `fuzz/fuzz_targets/fuzz_block_verification.rs` |
|
| `fuzz_block_verification` | Block hash integrity: HashRoundTrip · HashPreimage completeness (block_id/prev_hash/timestamp) · TxOrderCommitment | `fuzz/fuzz_targets/fuzz_block_verification.rs` |
|
||||||
|
|||||||
@ -103,7 +103,7 @@ just fuzz-regression
|
|||||||
|
|
||||||
| Target | What it fuzzes | Entry point |
|
| Target | What it fuzzes | Entry point |
|
||||||
|--------|---------------|-------------|
|
|--------|---------------|-------------|
|
||||||
| `fuzz_transaction_decoding` | Borsh decoding of `NSSATransaction`, `Block`, and `HashableBlockData`; roundtrip re-encoding of successfully decoded transactions | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` |
|
| `fuzz_transaction_decoding` | Borsh decoding of `LeeTransaction`, `Block`, and `HashableBlockData`; roundtrip re-encoding of successfully decoded transactions | `fuzz/fuzz_targets/fuzz_transaction_decoding.rs` |
|
||||||
| `fuzz_stateless_verification` | `transaction_stateless_check()` no-panic on arbitrary bytes; idempotency — a transaction that passes the check must pass it again | `fuzz/fuzz_targets/fuzz_stateless_verification.rs` |
|
| `fuzz_stateless_verification` | `transaction_stateless_check()` no-panic on arbitrary bytes; idempotency — a transaction that passes the check must pass it again | `fuzz/fuzz_targets/fuzz_stateless_verification.rs` |
|
||||||
| `fuzz_state_transition` | `execute_check_on_state()` across up to 8 transactions with fuzz-driven initial state and monotonically-advancing block context; asserts **StateIsolationOnFailure** (balances unchanged on rejection), **BalanceConservation** (total balance unchanged on success), and **ReplayRejection** (nonce consumed on first acceptance) | `fuzz/fuzz_targets/fuzz_state_transition.rs` |
|
| `fuzz_state_transition` | `execute_check_on_state()` across up to 8 transactions with fuzz-driven initial state and monotonically-advancing block context; asserts **StateIsolationOnFailure** (balances unchanged on rejection), **BalanceConservation** (total balance unchanged on success), and **ReplayRejection** (nonce consumed on first acceptance) | `fuzz/fuzz_targets/fuzz_state_transition.rs` |
|
||||||
| `fuzz_block_verification` | Three block-hash invariants: **HashRoundTrip** (`HashableBlockData::from(Block)` is lossless), **HashPreimage** (block_id, prev_block_hash, timestamp each individually affect the hash), **TxOrderCommitment** (reversing the transaction list changes the hash) | `fuzz/fuzz_targets/fuzz_block_verification.rs` |
|
| `fuzz_block_verification` | Three block-hash invariants: **HashRoundTrip** (`HashableBlockData::from(Block)` is lossless), **HashPreimage** (block_id, prev_block_hash, timestamp each individually affect the hash), **TxOrderCommitment** (reversing the transaction list changes the hash) | `fuzz/fuzz_targets/fuzz_block_verification.rs` |
|
||||||
@ -558,19 +558,19 @@ fuzz target parameters for zero-boilerplate structured fuzzing.
|
|||||||
| `ArbWitnessSet` | `WitnessSet` (0–3 `(Signature, PublicKey)` pairs; mixes valid and invalid) |
|
| `ArbWitnessSet` | `WitnessSet` (0–3 `(Signature, PublicKey)` pairs; mixes valid and invalid) |
|
||||||
| `ArbPublicTransaction` | `PublicTransaction` (composed from `ArbPubTxMessage` + `ArbWitnessSet`) |
|
| `ArbPublicTransaction` | `PublicTransaction` (composed from `ArbPubTxMessage` + `ArbWitnessSet`) |
|
||||||
| `ArbProgramDeploymentTransaction` | `ProgramDeploymentTransaction` (arbitrary bytecode) |
|
| `ArbProgramDeploymentTransaction` | `ProgramDeploymentTransaction` (arbitrary bytecode) |
|
||||||
| `ArbHashableBlockData` | `HashableBlockData` (0–7 `ArbNSSATransaction` entries, random header fields) |
|
| `ArbHashableBlockData` | `HashableBlockData` (0–7 `ArbLeeTransaction` entries, random header fields) |
|
||||||
| `ArbNSSATransaction` | `NSSATransaction` (`Public` or `ProgramDeployment` variant; `PrivacyPreserving` excluded) |
|
| `ArbLeeTransaction` | `LeeTransaction` (`Public` or `ProgramDeployment` variant; `PrivacyPreserving` excluded) |
|
||||||
|
|
||||||
### `fuzz_props::generators` (libFuzzer helpers + proptest strategies)
|
### `fuzz_props::generators` (libFuzzer helpers + proptest strategies)
|
||||||
|
|
||||||
| Generator | Covers |
|
| Generator | Covers |
|
||||||
|-----------|--------|
|
|-----------|--------|
|
||||||
| `arbitrary_fuzz_state()` | 1–8 fuzz-driven accounts with arbitrary IDs, balances, and private keys; used by `fuzz_state_transition`, `fuzz_replay_prevention`, `fuzz_validate_execute_consistency`, `fuzz_state_diff_computation` |
|
| `arbitrary_fuzz_state()` | 1–8 fuzz-driven accounts with arbitrary IDs, balances, and private keys; used by `fuzz_state_transition`, `fuzz_replay_prevention`, `fuzz_validate_execute_consistency`, `fuzz_state_diff_computation` |
|
||||||
| `arb_fuzz_native_transfer()` | Correctly-signed native-transfer `NSSATransaction` referencing accounts from an `arbitrary_fuzz_state()` result; gives the fuzzer a path to successful state transitions |
|
| `arb_fuzz_native_transfer()` | Correctly-signed native-transfer `LeeTransaction` referencing accounts from an `arbitrary_fuzz_state()` result; gives the fuzzer a path to successful state transitions |
|
||||||
| `arbitrary_transaction()` | Structured `NSSATransaction` (`Public` or `ProgramDeployment`) from unstructured bytes via `ArbNSSATransaction` |
|
| `arbitrary_transaction()` | Structured `LeeTransaction` (`Public` or `ProgramDeployment`) from unstructured bytes via `ArbLeeTransaction` |
|
||||||
| `arb_borsh_transaction_bytes()` | Raw Borsh bytes including invalid encodings |
|
| `arb_borsh_transaction_bytes()` | Raw Borsh bytes including invalid encodings |
|
||||||
| `signer_account_ids()` | Extracts `AccountId`s of all signers from an `NSSATransaction`'s witness set; used to derive signer IDs before `apply_state_diff` consumes the diff |
|
| `signer_account_ids()` | Extracts `AccountId`s of all signers from an `LeeTransaction`'s witness set; used to derive signer IDs before `apply_state_diff` consumes the diff |
|
||||||
| `arb_native_transfer_tx()` | Valid native-transfer `NSSATransaction` between known testnet genesis accounts (proptest strategy) |
|
| `arb_native_transfer_tx()` | Valid native-transfer `LeeTransaction` between known testnet genesis accounts (proptest strategy) |
|
||||||
| `test_accounts()` | Returns `(AccountId, PrivateKey)` pairs from `testnet_initial_state` |
|
| `test_accounts()` | Returns `(AccountId, PrivateKey)` pairs from `testnet_initial_state` |
|
||||||
| `arb_hashable_block_data()` | `HashableBlockData` with 0–8 valid native transfers (proptest strategy) |
|
| `arb_hashable_block_data()` | `HashableBlockData` with 0–8 valid native transfers (proptest strategy) |
|
||||||
| `arb_invalid_account_state_tx()` | Phantom accounts + overflow amounts — expected to be rejected (IS-3) |
|
| `arb_invalid_account_state_tx()` | Phantom accounts + overflow amounts — expected to be rejected (IS-3) |
|
||||||
|
|||||||
840
fuzz/Cargo.lock
generated
840
fuzz/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -44,11 +44,11 @@ libfuzzer-sys = { version = "0.4", optional = true }
|
|||||||
afl = { version = "0.15", optional = true }
|
afl = { version = "0.15", optional = true }
|
||||||
arbitrary = { version = "1", features = ["derive"] }
|
arbitrary = { version = "1", features = ["derive"] }
|
||||||
borsh = "1"
|
borsh = "1"
|
||||||
nssa = { path = "../../logos-execution-zone/nssa" }
|
nssa = { path = "../../logos-execution-zone/lee/state_machine", package = "lee" }
|
||||||
nssa_core = { path = "../../logos-execution-zone/nssa/core" }
|
nssa_core = { path = "../../logos-execution-zone/lee/state_machine/core", package = "lee_core" }
|
||||||
common = { path = "../../logos-execution-zone/common" }
|
common = { path = "../../logos-execution-zone/lez/common" }
|
||||||
fuzz_props = { path = "../fuzz_props" }
|
fuzz_props = { path = "../fuzz_props" }
|
||||||
testnet_initial_state = { path = "../../logos-execution-zone/testnet_initial_state" }
|
testnet_initial_state = { path = "../../logos-execution-zone/lez/testnet_initial_state" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use arbitrary::{Arbitrary, Unstructured};
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
use fuzz_props::arbitrary_types::ArbNSSATransaction;
|
use fuzz_props::arbitrary_types::ArbLeeTransaction;
|
||||||
use fuzz_props::generators::{arbitrary_fuzz_state, signer_account_ids};
|
use fuzz_props::generators::{arbitrary_fuzz_state, signer_account_ids};
|
||||||
use fuzz_props::invariants::{NonceSnapshot, assert_nonce_increment_correctness};
|
use fuzz_props::invariants::{NonceSnapshot, assert_nonce_increment_correctness};
|
||||||
use nssa::V03State;
|
use nssa::V03State;
|
||||||
@ -52,7 +52,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Generate and stateless-check a transaction.
|
// Generate and stateless-check a transaction.
|
||||||
let tx_raw = match ArbNSSATransaction::arbitrary(&mut u) {
|
let tx_raw = match ArbLeeTransaction::arbitrary(&mut u) {
|
||||||
Ok(w) => w.0,
|
Ok(w) => w.0,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use arbitrary::{Arbitrary, Unstructured};
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
use common::transaction::{NSSATransaction, clock_invocation};
|
use common::transaction::{LeeTransaction, clock_invocation};
|
||||||
use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction};
|
use fuzz_props::generators::{arb_fuzz_native_transfer, arbitrary_fuzz_state, arbitrary_transaction};
|
||||||
use nssa::V03State;
|
use nssa::V03State;
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
|||||||
|
|
||||||
// Accepted transaction list — populated here, consumed by the replayer phase
|
// Accepted transaction list — populated here, consumed by the replayer phase
|
||||||
// so that both pipelines process exactly the same set of transactions.
|
// so that both pipelines process exactly the same set of transactions.
|
||||||
let mut accepted_txs: Vec<NSSATransaction> = Vec::new();
|
let mut accepted_txs: Vec<LeeTransaction> = Vec::new();
|
||||||
|
|
||||||
let n_txs: u8 = u8::arbitrary(&mut u).unwrap_or(0) % 8;
|
let n_txs: u8 = u8::arbitrary(&mut u).unwrap_or(0) % 8;
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
//! specific account shapes such as zero balance or `u128::MAX` — are reachable.
|
//! specific account shapes such as zero balance or `u128::MAX` — are reachable.
|
||||||
|
|
||||||
use arbitrary::{Arbitrary, Unstructured};
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
use common::transaction::NSSATransaction;
|
use common::transaction::LeeTransaction;
|
||||||
use fuzz_props::arbitrary_types::ArbPublicTransaction;
|
use fuzz_props::arbitrary_types::ArbPublicTransaction;
|
||||||
use fuzz_props::generators::arbitrary_fuzz_state;
|
use fuzz_props::generators::arbitrary_fuzz_state;
|
||||||
use nssa::{V03State, ValidatedStateDiff};
|
use nssa::{V03State, ValidatedStateDiff};
|
||||||
@ -47,7 +47,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
|||||||
// Collect the set of accounts the transaction declares it will touch.
|
// Collect the set of accounts the transaction declares it will touch.
|
||||||
// `affected_public_account_ids()` returns owned data so `pub_tx` remains
|
// `affected_public_account_ids()` returns owned data so `pub_tx` remains
|
||||||
// available for both `from_public_transaction` (borrow) and the later move
|
// available for both `from_public_transaction` (borrow) and the later move
|
||||||
// into `NSSATransaction::Public`.
|
// into `LeeTransaction::Public`.
|
||||||
let affected = pub_tx.affected_public_account_ids();
|
let affected = pub_tx.affected_public_account_ids();
|
||||||
|
|
||||||
match ValidatedStateDiff::from_public_transaction(&pub_tx, &state, 1, 0) {
|
match ValidatedStateDiff::from_public_transaction(&pub_tx, &state, 1, 0) {
|
||||||
@ -77,7 +77,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
|||||||
// we do not panic on a structurally malformed transaction.
|
// we do not panic on a structurally malformed transaction.
|
||||||
let mut exec_state = state.clone();
|
let mut exec_state = state.clone();
|
||||||
// `pub_tx` is moved here; it is no longer borrowed after this point.
|
// `pub_tx` is moved here; it is no longer borrowed after this point.
|
||||||
let tx_for_exec = NSSATransaction::Public(pub_tx);
|
let tx_for_exec = LeeTransaction::Public(pub_tx);
|
||||||
if let Ok(checked_tx) = tx_for_exec.transaction_stateless_check() {
|
if let Ok(checked_tx) = tx_for_exec.transaction_stateless_check() {
|
||||||
if checked_tx.execute_check_on_state(&mut exec_state, 1, 0).is_ok() {
|
if checked_tx.execute_check_on_state(&mut exec_state, 1, 0).is_ok() {
|
||||||
for acc_id in &affected {
|
for acc_id in &affected {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)]
|
#![cfg_attr(feature = "fuzzer-libfuzzer", no_main)]
|
||||||
|
|
||||||
use arbitrary::Unstructured;
|
use arbitrary::Unstructured;
|
||||||
use common::transaction::NSSATransaction;
|
use common::transaction::LeeTransaction;
|
||||||
use fuzz_props::generators::arbitrary_transaction;
|
use fuzz_props::generators::arbitrary_transaction;
|
||||||
|
|
||||||
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||||
@ -22,7 +22,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Path B: raw decode first, then check — must never panic
|
// Path B: raw decode first, then check — must never panic
|
||||||
if let Ok(tx) = borsh::from_slice::<NSSATransaction>(data) {
|
if let Ok(tx) = borsh::from_slice::<LeeTransaction>(data) {
|
||||||
let _ = tx.transaction_stateless_check();
|
let _ = tx.transaction_stateless_check();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,19 +2,19 @@
|
|||||||
|
|
||||||
use common::{
|
use common::{
|
||||||
block::{Block, HashableBlockData},
|
block::{Block, HashableBlockData},
|
||||||
transaction::NSSATransaction,
|
transaction::LeeTransaction,
|
||||||
};
|
};
|
||||||
|
|
||||||
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
fuzz_props::fuzz_entry!(|data: &[u8]| {
|
||||||
// Attempt 1: decode as NSSATransaction and verify roundtrip
|
// Attempt 1: decode as LeeTransaction and verify roundtrip
|
||||||
if let Ok(tx) = borsh::from_slice::<NSSATransaction>(data) {
|
if let Ok(tx) = borsh::from_slice::<LeeTransaction>(data) {
|
||||||
let re_encoded = borsh::to_vec(&tx).expect("re-encode of valid tx must succeed");
|
let re_encoded = borsh::to_vec(&tx).expect("re-encode of valid tx must succeed");
|
||||||
let tx2 = borsh::from_slice::<NSSATransaction>(&re_encoded)
|
let tx2 = borsh::from_slice::<LeeTransaction>(&re_encoded)
|
||||||
.expect("second decode of re-encoded tx must succeed");
|
.expect("second decode of re-encoded tx must succeed");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
re_encoded,
|
re_encoded,
|
||||||
borsh::to_vec(&tx2).unwrap(),
|
borsh::to_vec(&tx2).unwrap(),
|
||||||
"NSSATransaction roundtrip encoding divergence"
|
"LeeTransaction roundtrip encoding divergence"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
//! reachable by the fuzzer.
|
//! reachable by the fuzzer.
|
||||||
|
|
||||||
use arbitrary::{Arbitrary, Unstructured};
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
use fuzz_props::arbitrary_types::ArbNSSATransaction;
|
use fuzz_props::arbitrary_types::ArbLeeTransaction;
|
||||||
use fuzz_props::generators::{arbitrary_fuzz_state, signer_account_ids};
|
use fuzz_props::generators::{arbitrary_fuzz_state, signer_account_ids};
|
||||||
use fuzz_props::invariants::{NonceSnapshot, assert_nonce_increment_correctness};
|
use fuzz_props::invariants::{NonceSnapshot, assert_nonce_increment_correctness};
|
||||||
use nssa::V03State;
|
use nssa::V03State;
|
||||||
@ -47,7 +47,7 @@ fuzz_props::fuzz_entry!(|data: &[u8]| {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Generate the transaction from the remaining fuzz bytes.
|
// Generate the transaction from the remaining fuzz bytes.
|
||||||
let tx = match ArbNSSATransaction::arbitrary(&mut u) {
|
let tx = match ArbLeeTransaction::arbitrary(&mut u) {
|
||||||
Ok(w) => w.0,
|
Ok(w) => w.0,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! **No changes to `../logos-execution-zone` are required.**
|
//! **No changes to `../logos-execution-zone` are required.**
|
||||||
//!
|
//!
|
||||||
//! The Rust orphan rule forbids `impl Arbitrary for NSSATransaction` when both
|
//! The Rust orphan rule forbids `impl Arbitrary for LeeTransaction` when both
|
||||||
//! the trait and the type come from external crates. Using newtypes (`ArbXxx`)
|
//! the trait and the type come from external crates. Using newtypes (`ArbXxx`)
|
||||||
//! sidesteps the restriction entirely.
|
//! sidesteps the restriction entirely.
|
||||||
//!
|
//!
|
||||||
@ -10,10 +10,10 @@
|
|||||||
//!
|
//!
|
||||||
//! ```rust,ignore
|
//! ```rust,ignore
|
||||||
//! #![no_main]
|
//! #![no_main]
|
||||||
//! use fuzz_props::arbitrary_types::ArbNSSATransaction;
|
//! use fuzz_props::arbitrary_types::ArbLeeTransaction;
|
||||||
//! use libfuzzer_sys::fuzz_target;
|
//! use libfuzzer_sys::fuzz_target;
|
||||||
//!
|
//!
|
||||||
//! fuzz_target!(|wrapped: ArbNSSATransaction| {
|
//! fuzz_target!(|wrapped: ArbLeeTransaction| {
|
||||||
//! let tx = wrapped.0;
|
//! let tx = wrapped.0;
|
||||||
//! let Ok(valid_tx) = tx.transaction_stateless_check() else { return; };
|
//! let Ok(valid_tx) = tx.transaction_stateless_check() else { return; };
|
||||||
//! // …
|
//! // …
|
||||||
@ -21,7 +21,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use arbitrary::{Arbitrary, Result as ArbResult, Unstructured};
|
use arbitrary::{Arbitrary, Result as ArbResult, Unstructured};
|
||||||
use common::{HashType, block::HashableBlockData, transaction::NSSATransaction};
|
use common::{HashType, block::HashableBlockData, transaction::LeeTransaction};
|
||||||
use nssa::{
|
use nssa::{
|
||||||
AccountId, PrivateKey, PublicKey, Signature,
|
AccountId, PrivateKey, PublicKey, Signature,
|
||||||
program_deployment_transaction::ProgramDeploymentTransaction,
|
program_deployment_transaction::ProgramDeploymentTransaction,
|
||||||
@ -210,24 +210,24 @@ impl<'a> Arbitrary<'a> for ArbProgramDeploymentTransaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── NSSATransaction ───────────────────────────────────────────────────────────
|
// ── LeeTransaction ───────────────────────────────────────────────────────────
|
||||||
// `PrivacyPreservingTransaction` is intentionally excluded: it embeds a risc0
|
// `PrivacyPreservingTransaction` is intentionally excluded: it embeds a risc0
|
||||||
// ZK receipt that cannot be generated inside a hot fuzzing loop. This matches
|
// ZK receipt that cannot be generated inside a hot fuzzing loop. This matches
|
||||||
// the known limitation documented in `docs/fuzzing.md`.
|
// the known limitation documented in `docs/fuzzing.md`.
|
||||||
|
|
||||||
/// Newtype wrapper providing [`Arbitrary`] for [`NSSATransaction`].
|
/// Newtype wrapper providing [`Arbitrary`] for [`LeeTransaction`].
|
||||||
///
|
///
|
||||||
/// Generates `Public` and `ProgramDeployment` variants only.
|
/// Generates `Public` and `ProgramDeployment` variants only.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ArbNSSATransaction(pub NSSATransaction);
|
pub struct ArbLeeTransaction(pub LeeTransaction);
|
||||||
|
|
||||||
impl<'a> Arbitrary<'a> for ArbNSSATransaction {
|
impl<'a> Arbitrary<'a> for ArbLeeTransaction {
|
||||||
fn arbitrary(u: &mut Unstructured<'a>) -> ArbResult<Self> {
|
fn arbitrary(u: &mut Unstructured<'a>) -> ArbResult<Self> {
|
||||||
match u8::arbitrary(u)? % 2 {
|
match u8::arbitrary(u)? % 2 {
|
||||||
0 => Ok(Self(NSSATransaction::Public(
|
0 => Ok(Self(LeeTransaction::Public(
|
||||||
ArbPublicTransaction::arbitrary(u)?.0,
|
ArbPublicTransaction::arbitrary(u)?.0,
|
||||||
))),
|
))),
|
||||||
_ => Ok(Self(NSSATransaction::ProgramDeployment(
|
_ => Ok(Self(LeeTransaction::ProgramDeployment(
|
||||||
ArbProgramDeploymentTransaction::arbitrary(u)?.0,
|
ArbProgramDeploymentTransaction::arbitrary(u)?.0,
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
@ -246,7 +246,7 @@ impl<'a> Arbitrary<'a> for ArbHashableBlockData {
|
|||||||
fn arbitrary(u: &mut Unstructured<'a>) -> ArbResult<Self> {
|
fn arbitrary(u: &mut Unstructured<'a>) -> ArbResult<Self> {
|
||||||
// 0–7 transactions per block
|
// 0–7 transactions per block
|
||||||
let n = (u8::arbitrary(u)? as usize) % 8;
|
let n = (u8::arbitrary(u)? as usize) % 8;
|
||||||
let transactions = std::iter::repeat_with(|| ArbNSSATransaction::arbitrary(u).map(|t| t.0))
|
let transactions = std::iter::repeat_with(|| ArbLeeTransaction::arbitrary(u).map(|t| t.0))
|
||||||
.take(n)
|
.take(n)
|
||||||
.collect::<ArbResult<Vec<_>>>()?;
|
.collect::<ArbResult<Vec<_>>>()?;
|
||||||
Ok(Self(HashableBlockData {
|
Ok(Self(HashableBlockData {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
use arbitrary::{Arbitrary, Unstructured};
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
use common::{block::HashableBlockData, transaction::NSSATransaction};
|
use common::{block::HashableBlockData, transaction::LeeTransaction};
|
||||||
use nssa::{AccountId, PrivateKey};
|
use nssa::{AccountId, PrivateKey};
|
||||||
|
|
||||||
use crate::arbitrary_types::{ArbAccountId, ArbNSSATransaction, ArbPrivateKey};
|
use crate::arbitrary_types::{ArbAccountId, ArbLeeTransaction, ArbPrivateKey};
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
use testnet_initial_state::initial_pub_accounts_private_keys;
|
use testnet_initial_state::initial_pub_accounts_private_keys;
|
||||||
|
|
||||||
@ -12,22 +12,22 @@ use testnet_initial_state::initial_pub_accounts_private_keys;
|
|||||||
/// witness set. Used by fuzz targets that need to verify nonce
|
/// witness set. Used by fuzz targets that need to verify nonce
|
||||||
/// increments after `execute_check_on_state`.
|
/// increments after `execute_check_on_state`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn signer_account_ids(tx: &common::transaction::NSSATransaction) -> Vec<nssa::AccountId> {
|
pub fn signer_account_ids(tx: &common::transaction::LeeTransaction) -> Vec<nssa::AccountId> {
|
||||||
use common::transaction::NSSATransaction;
|
use common::transaction::LeeTransaction;
|
||||||
match tx {
|
match tx {
|
||||||
NSSATransaction::Public(pt) => pt
|
LeeTransaction::Public(pt) => pt
|
||||||
.witness_set()
|
.witness_set()
|
||||||
.signatures_and_public_keys()
|
.signatures_and_public_keys()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, pk)| nssa::AccountId::from(pk))
|
.map(|(_, pk)| nssa::AccountId::from(pk))
|
||||||
.collect(),
|
.collect(),
|
||||||
NSSATransaction::PrivacyPreserving(pt) => pt
|
LeeTransaction::PrivacyPreserving(pt) => pt
|
||||||
.witness_set()
|
.witness_set()
|
||||||
.signatures_and_public_keys()
|
.signatures_and_public_keys()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, pk)| nssa::AccountId::from(pk))
|
.map(|(_, pk)| nssa::AccountId::from(pk))
|
||||||
.collect(),
|
.collect(),
|
||||||
NSSATransaction::ProgramDeployment(_) => vec![],
|
LeeTransaction::ProgramDeployment(_) => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ pub fn arbitrary_fuzz_state(u: &mut Unstructured<'_>) -> arbitrary::Result<Vec<F
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a native-transfer [`NSSATransaction`] between two accounts chosen
|
/// Generate a native-transfer [`LeeTransaction`] between two accounts chosen
|
||||||
/// from `accounts`.
|
/// from `accounts`.
|
||||||
///
|
///
|
||||||
/// Because every account in the slice has a known private key, the resulting
|
/// Because every account in the slice has a known private key, the resulting
|
||||||
@ -87,7 +87,7 @@ pub fn arbitrary_fuzz_state(u: &mut Unstructured<'_>) -> arbitrary::Result<Vec<F
|
|||||||
pub fn arb_fuzz_native_transfer(
|
pub fn arb_fuzz_native_transfer(
|
||||||
u: &mut Unstructured<'_>,
|
u: &mut Unstructured<'_>,
|
||||||
accounts: &[FuzzAccount],
|
accounts: &[FuzzAccount],
|
||||||
) -> arbitrary::Result<NSSATransaction> {
|
) -> arbitrary::Result<LeeTransaction> {
|
||||||
if accounts.is_empty() {
|
if accounts.is_empty() {
|
||||||
return Err(arbitrary::Error::IncorrectFormat);
|
return Err(arbitrary::Error::IncorrectFormat);
|
||||||
}
|
}
|
||||||
@ -112,9 +112,9 @@ pub fn arb_fuzz_native_transfer(
|
|||||||
|
|
||||||
// ── Arbitrary (for libFuzzer targets) ────────────────────────────────────────
|
// ── Arbitrary (for libFuzzer targets) ────────────────────────────────────────
|
||||||
|
|
||||||
/// Generate a structurally plausible `NSSATransaction` from unstructured bytes.
|
/// Generate a structurally plausible `LeeTransaction` from unstructured bytes.
|
||||||
pub fn arbitrary_transaction(u: &mut Unstructured<'_>) -> arbitrary::Result<NSSATransaction> {
|
pub fn arbitrary_transaction(u: &mut Unstructured<'_>) -> arbitrary::Result<LeeTransaction> {
|
||||||
ArbNSSATransaction::arbitrary(u).map(|w| w.0)
|
ArbLeeTransaction::arbitrary(u).map(|w| w.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── proptest strategies ───────────────────────────────────────────────────────
|
// ── proptest strategies ───────────────────────────────────────────────────────
|
||||||
@ -128,7 +128,7 @@ prop_compose! {
|
|||||||
to_idx in 0..accounts.len(),
|
to_idx in 0..accounts.len(),
|
||||||
nonce in 0_u128..1_000_u128,
|
nonce in 0_u128..1_000_u128,
|
||||||
amount in 0_u128..10_000_u128,
|
amount in 0_u128..10_000_u128,
|
||||||
) -> NSSATransaction {
|
) -> LeeTransaction {
|
||||||
let (from_id, from_key) = &accounts[from_idx];
|
let (from_id, from_key) = &accounts[from_idx];
|
||||||
let (to_id, _) = &accounts[to_idx];
|
let (to_id, _) = &accounts[to_idx];
|
||||||
common::test_utils::create_transaction_native_token_transfer(
|
common::test_utils::create_transaction_native_token_transfer(
|
||||||
@ -146,11 +146,11 @@ pub fn test_accounts() -> Vec<(AccountId, PrivateKey)> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Strategy: raw bytes that are valid borsh encodings of `NSSATransaction`.
|
/// Strategy: raw bytes that are valid borsh encodings of `LeeTransaction`.
|
||||||
pub fn arb_borsh_transaction_bytes() -> impl Strategy<Value = Vec<u8>> {
|
pub fn arb_borsh_transaction_bytes() -> impl Strategy<Value = Vec<u8>> {
|
||||||
any::<Vec<u8>>().prop_map(|bytes| {
|
any::<Vec<u8>>().prop_map(|bytes| {
|
||||||
// Either pass through raw bytes OR encode a known dummy transaction
|
// Either pass through raw bytes OR encode a known dummy transaction
|
||||||
if borsh::from_slice::<NSSATransaction>(&bytes).is_ok() {
|
if borsh::from_slice::<LeeTransaction>(&bytes).is_ok() {
|
||||||
bytes
|
bytes
|
||||||
} else {
|
} else {
|
||||||
borsh::to_vec(&common::test_utils::produce_dummy_empty_transaction()).unwrap()
|
borsh::to_vec(&common::test_utils::produce_dummy_empty_transaction()).unwrap()
|
||||||
@ -183,7 +183,7 @@ prop_compose! {
|
|||||||
phantom_id_bytes in proptest::array::uniform32(0_u8..),
|
phantom_id_bytes in proptest::array::uniform32(0_u8..),
|
||||||
amount in (u128::MAX / 2)..u128::MAX, // overflow-inducing amount
|
amount in (u128::MAX / 2)..u128::MAX, // overflow-inducing amount
|
||||||
nonce in 0_u128..10_u128,
|
nonce in 0_u128..10_u128,
|
||||||
) -> NSSATransaction {
|
) -> LeeTransaction {
|
||||||
let phantom_id = nssa::AccountId::new(phantom_id_bytes);
|
let phantom_id = nssa::AccountId::new(phantom_id_bytes);
|
||||||
// Attempt to sign with a key that has no matching on-chain account
|
// Attempt to sign with a key that has no matching on-chain account
|
||||||
let signing_key = nssa::PrivateKey::try_new(phantom_id_bytes)
|
let signing_key = nssa::PrivateKey::try_new(phantom_id_bytes)
|
||||||
@ -204,11 +204,11 @@ prop_compose! {
|
|||||||
/// attack candidates) and some are re-ordered permutations of a valid sequence.
|
/// attack candidates) and some are re-ordered permutations of a valid sequence.
|
||||||
/// Used in proptest-level tests and as a seed generator for the state-transition
|
/// Used in proptest-level tests and as a seed generator for the state-transition
|
||||||
/// fuzz target.
|
/// fuzz target.
|
||||||
pub fn arb_duplicate_tx_sequence() -> impl Strategy<Value = Vec<NSSATransaction>> {
|
pub fn arb_duplicate_tx_sequence() -> impl Strategy<Value = Vec<LeeTransaction>> {
|
||||||
let accounts = test_accounts();
|
let accounts = test_accounts();
|
||||||
proptest::collection::vec(arb_native_transfer_tx(accounts), 1..5_usize).prop_flat_map(|txs| {
|
proptest::collection::vec(arb_native_transfer_tx(accounts), 1..5_usize).prop_flat_map(|txs| {
|
||||||
// Build a sequence that: original | duplicates | reversed
|
// Build a sequence that: original | duplicates | reversed
|
||||||
let duped: Vec<NSSATransaction> = txs
|
let duped: Vec<LeeTransaction> = txs
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.chain(txs.iter().cloned()) // append exact duplicates
|
.chain(txs.iter().cloned()) // append exact duplicates
|
||||||
@ -225,7 +225,7 @@ pub fn arb_duplicate_tx_sequence() -> impl Strategy<Value = Vec<NSSATransaction>
|
|||||||
/// - self-transfers (sender == recipient),
|
/// - self-transfers (sender == recipient),
|
||||||
/// - max-nonce wrapping,
|
/// - max-nonce wrapping,
|
||||||
/// - alternating valid / invalid transactions to test partial-batch isolation.
|
/// - alternating valid / invalid transactions to test partial-batch isolation.
|
||||||
pub fn arb_pathological_sequence() -> impl Strategy<Value = Vec<NSSATransaction>> {
|
pub fn arb_pathological_sequence() -> impl Strategy<Value = Vec<LeeTransaction>> {
|
||||||
let accounts = test_accounts();
|
let accounts = test_accounts();
|
||||||
let n = accounts.len();
|
let n = accounts.len();
|
||||||
proptest::collection::vec((0..n, 0..n, 0_u128..5_u128, any::<bool>()), 1..8_usize).prop_map(
|
proptest::collection::vec((0..n, 0..n, 0_u128..5_u128, any::<bool>()), 1..8_usize).prop_map(
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use common::transaction::NSSATransaction;
|
use common::transaction::LeeTransaction;
|
||||||
use nssa::V03State;
|
use nssa::V03State;
|
||||||
use nssa_core::account::Nonce;
|
use nssa_core::account::Nonce;
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ impl ProtocolInvariant for FailedTxNonceStability {
|
|||||||
/// # Enforcement
|
/// # Enforcement
|
||||||
///
|
///
|
||||||
/// This invariant **cannot** be enforced through [`InvariantCtx`] because the replay
|
/// This invariant **cannot** be enforced through [`InvariantCtx`] because the replay
|
||||||
/// check requires re-applying the `NSSATransaction` that `execute_check_on_state`
|
/// check requires re-applying the `LeeTransaction` that `execute_check_on_state`
|
||||||
/// consumes and returns on `Ok`. It is therefore **not registered** in
|
/// consumes and returns on `Ok`. It is therefore **not registered** in
|
||||||
/// [`assert_invariants`]; calling `assert_invariants` alone does **not** cover
|
/// [`assert_invariants`]; calling `assert_invariants` alone does **not** cover
|
||||||
/// `ReplayRejection`.
|
/// `ReplayRejection`.
|
||||||
@ -235,7 +235,7 @@ pub struct NonceIncrementCorrectness;
|
|||||||
///
|
///
|
||||||
/// # Why a standalone function?
|
/// # Why a standalone function?
|
||||||
///
|
///
|
||||||
/// `execute_check_on_state` consumes the `NSSATransaction` and returns it on `Ok`,
|
/// `execute_check_on_state` consumes the `LeeTransaction` and returns it on `Ok`,
|
||||||
/// so the transaction is not available as a shared reference inside [`InvariantCtx`].
|
/// so the transaction is not available as a shared reference inside [`InvariantCtx`].
|
||||||
/// This function accepts ownership of the returned transaction and performs the
|
/// This function accepts ownership of the returned transaction and performs the
|
||||||
/// replay in-place.
|
/// replay in-place.
|
||||||
@ -249,7 +249,7 @@ pub struct NonceIncrementCorrectness;
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn assert_replay_rejection(
|
pub fn assert_replay_rejection(
|
||||||
applied_tx: NSSATransaction,
|
applied_tx: LeeTransaction,
|
||||||
state: &mut V03State,
|
state: &mut V03State,
|
||||||
next_block_id: u64,
|
next_block_id: u64,
|
||||||
next_timestamp: u64,
|
next_timestamp: u64,
|
||||||
@ -270,7 +270,7 @@ pub fn assert_replay_rejection(
|
|||||||
/// passing the signer IDs derived from the transaction's witness set, the [`NonceSnapshot`]
|
/// passing the signer IDs derived from the transaction's witness set, the [`NonceSnapshot`]
|
||||||
/// captured **before** execution, and the post-execution state.
|
/// captured **before** execution, and the post-execution state.
|
||||||
///
|
///
|
||||||
/// For a `NSSATransaction::Public(tx)`, derive signer IDs as:
|
/// For a `LeeTransaction::Public(tx)`, derive signer IDs as:
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// let signer_ids: Vec<nssa::AccountId> = tx
|
/// let signer_ids: Vec<nssa::AccountId> = tx
|
||||||
@ -281,7 +281,7 @@ pub fn assert_replay_rejection(
|
|||||||
/// .collect();
|
/// .collect();
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// For `NSSATransaction::ProgramDeployment`, there are no signers; pass an empty slice.
|
/// For `LeeTransaction::ProgramDeployment`, there are no signers; pass an empty slice.
|
||||||
///
|
///
|
||||||
/// # Why a standalone function?
|
/// # Why a standalone function?
|
||||||
///
|
///
|
||||||
@ -377,7 +377,7 @@ pub fn assert_tx_execution_invariants<E>(
|
|||||||
state_after: &mut V03State,
|
state_after: &mut V03State,
|
||||||
balances_before: BalanceSnapshot,
|
balances_before: BalanceSnapshot,
|
||||||
nonces_before: NonceSnapshot,
|
nonces_before: NonceSnapshot,
|
||||||
execution_result: Result<NSSATransaction, E>,
|
execution_result: Result<LeeTransaction, E>,
|
||||||
replay_context: (u64, u64),
|
replay_context: (u64, u64),
|
||||||
) {
|
) {
|
||||||
let execution_succeeded = execution_result.is_ok();
|
let execution_succeeded = execution_result.is_ok();
|
||||||
@ -400,19 +400,19 @@ pub fn assert_tx_execution_invariants<E>(
|
|||||||
if let Ok(applied_tx) = execution_result {
|
if let Ok(applied_tx) = execution_result {
|
||||||
// Derive signer IDs from the witness set. ProgramDeployment has no signers.
|
// Derive signer IDs from the witness set. ProgramDeployment has no signers.
|
||||||
let signer_ids: Vec<nssa::AccountId> = match &applied_tx {
|
let signer_ids: Vec<nssa::AccountId> = match &applied_tx {
|
||||||
NSSATransaction::Public(pt) => pt
|
LeeTransaction::Public(pt) => pt
|
||||||
.witness_set()
|
.witness_set()
|
||||||
.signatures_and_public_keys()
|
.signatures_and_public_keys()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, pk)| nssa::AccountId::from(pk))
|
.map(|(_, pk)| nssa::AccountId::from(pk))
|
||||||
.collect(),
|
.collect(),
|
||||||
NSSATransaction::PrivacyPreserving(pt) => pt
|
LeeTransaction::PrivacyPreserving(pt) => pt
|
||||||
.witness_set()
|
.witness_set()
|
||||||
.signatures_and_public_keys()
|
.signatures_and_public_keys()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_, pk)| nssa::AccountId::from(pk))
|
.map(|(_, pk)| nssa::AccountId::from(pk))
|
||||||
.collect(),
|
.collect(),
|
||||||
NSSATransaction::ProgramDeployment(_) => vec![],
|
LeeTransaction::ProgramDeployment(_) => vec![],
|
||||||
};
|
};
|
||||||
assert_nonce_increment_correctness(&signer_ids, &nonces_for_nonce_check, state_after);
|
assert_nonce_increment_correctness(&signer_ids, &nonces_for_nonce_check, state_after);
|
||||||
let (next_block_id, next_timestamp) = replay_context;
|
let (next_block_id, next_timestamp) = replay_context;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user