• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Neptune-Crypto / neptune-core / 14686931002

27 Apr 2025 01:18AM UTC coverage: 79.739% (-5.2%) from 84.945%
14686931002

push

github

dan-da
refactor: simplify job-queue and log unique job-id

These changes were motivated by a need to follow/debug job-queue
behavior in the logs when shared by all async unit tests.

Primary changes:
1. Each job is now assigned a randomly generated 12-byte job-id that is
   logged when adding a job and when it begins and completes
   processing.
2. Simplifies queue implementation by removing the "add_job" spawned
   task.  There is now just a single "process_job" task, and jobs are
   added to the queue, via mutex, in the caller's task.
3. The process_job task listens for a Stop message and will stop
   right away, after signaling running job to cancel, if any.
4. Adds an async stop() method that also drops the queue. It performs
   cleanup better than a simple drop().
5. improve error handling.

Squashed:

* refactor job-queue so there is only one spawned task
* separate add-job and stop into separate channels
* add job_id to jobs, log
* job-queue cleanups
* wait for current job to complete when stopping queue

84 of 124 new or added lines in 1 file covered. (67.74%)

331 existing lines in 31 files now uncovered.

37191 of 46641 relevant lines covered (79.74%)

231785.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.58
/src/models/blockchain/block/validity/block_program.rs
1
use std::sync::OnceLock;
2

3
use itertools::Itertools;
4
use tasm_lib::field;
5
use tasm_lib::hashing::algebraic_hasher::hash_varlen::HashVarlen;
6
use tasm_lib::hashing::hash_from_stack::HashFromStack;
7
use tasm_lib::hashing::merkle_verify::MerkleVerify;
8
use tasm_lib::memory::FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS;
9
use tasm_lib::prelude::DataType;
10
use tasm_lib::prelude::Digest;
11
use tasm_lib::prelude::Library;
12
use tasm_lib::triton_vm::isa::triton_asm;
13
use tasm_lib::triton_vm::isa::triton_instr;
14
use tasm_lib::triton_vm::prelude::BFieldCodec;
15
use tasm_lib::triton_vm::prelude::LabelledInstruction;
16
use tasm_lib::triton_vm::prelude::Tip5;
17
use tasm_lib::triton_vm::proof::Claim;
18
use tasm_lib::triton_vm::stark::Stark;
19
use tasm_lib::verifier::stark_verify::StarkVerify;
20
use tracing::debug;
21

22
use super::block_proof_witness::BlockProofWitness;
23
use crate::config_models::network::Network;
24
use crate::models::blockchain::block::block_body::BlockBody;
25
use crate::models::blockchain::block::block_body::BlockBodyField;
26
use crate::models::blockchain::block::BlockAppendix;
27
use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel;
28
use crate::models::blockchain::transaction::transaction_kernel::TransactionKernelField;
29
use crate::models::blockchain::transaction::validity::neptune_proof::Proof;
30
use crate::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount;
31
use crate::models::proof_abstractions::mast_hash::MastHash;
32
use crate::models::proof_abstractions::tasm::program::ConsensusProgram;
33
use crate::models::proof_abstractions::verifier::verify;
34

35
/// Verifies that all claims listed in the appendix are true.
36
///
37
/// The witness for this program is [`BlockProofWitness`].
38
#[derive(Debug, Clone, Copy)]
39
pub(crate) struct BlockProgram;
40

41
impl BlockProgram {
42
    const ILLEGAL_FEE: i128 = 1_000_210;
43
    const PROOF_SIZE_INDICATOR_TOO_BIG: i128 = 1_000_211;
44

45
    pub(crate) fn claim(block_body: &BlockBody, appendix: &BlockAppendix) -> Claim {
229✔
46
        Claim::new(Self.hash())
229✔
47
            .with_input(block_body.mast_hash().reversed().values().to_vec())
229✔
48
            .with_output(appendix.claims_as_output())
229✔
49
    }
229✔
50

51
    pub(crate) async fn verify(
64✔
52
        block_body: &BlockBody,
64✔
53
        appendix: &BlockAppendix,
64✔
54
        proof: &Proof,
64✔
55
        network: Network,
64✔
56
    ) -> bool {
76✔
57
        let claim = Self::claim(block_body, appendix);
76✔
58
        let proof_clone = proof.clone();
76✔
59

76✔
60
        debug!("** Calling triton_vm::verify to verify block proof ...");
76✔
61
        let verdict = verify(claim, proof_clone, network).await;
76✔
62
        debug!("** Call to triton_vm::verify to verify block proof completed; verdict: {verdict}.");
76✔
63

64
        verdict
76✔
65
    }
76✔
66
}
67

68
impl ConsensusProgram for BlockProgram {
69
    fn library_and_code(&self) -> (Library, Vec<LabelledInstruction>) {
81✔
70
        // restrict proof size to avoid jumping backwards or to arbitrary
71
        // place in memory.
72
        const MAX_PROOF_SIZE: u64 = 4_000_000;
73

74
        let mut library = Library::new();
81✔
75

81✔
76
        let stark_verify = library.import(Box::new(StarkVerify::new_with_dynamic_layout(
81✔
77
            Stark::default(),
81✔
78
        )));
81✔
79

81✔
80
        let block_body_field = field!(BlockProofWitness::block_body);
81✔
81
        let body_field_kernel = field!(BlockBody::transaction_kernel);
81✔
82
        let kernel_field_fee = field!(TransactionKernel::fee);
81✔
83
        let block_witness_field_claims = field!(BlockProofWitness::claims);
81✔
84
        let block_witness_field_proofs = field!(BlockProofWitness::proofs);
81✔
85

81✔
86
        let merkle_verify = library.import(Box::new(MerkleVerify));
81✔
87
        let coin_size = NativeCurrencyAmount::static_length().unwrap();
81✔
88
        let push_max_amount = NativeCurrencyAmount::max().push_to_stack();
81✔
89
        let u128_lt = library.import(Box::new(tasm_lib::arithmetic::u128::lt::Lt));
81✔
90
        let hash_from_stack_digest = library.import(Box::new(HashFromStack::new(DataType::Digest)));
81✔
91
        let hash_from_stack_amount = library.import(Box::new(HashFromStack::new(DataType::I128)));
81✔
92
        let verify_fee_legality = triton_asm!(
81✔
93
            // _ *w [txkmh]
81✔
94

81✔
95
            dup 4
81✔
96
            dup 4
81✔
97
            dup 4
81✔
98
            dup 4
81✔
99
            dup 4
81✔
100
            push {TransactionKernel::MAST_HEIGHT}
81✔
101
            // _ *w [txkmh] [txkmh] txkm_height
81✔
102

81✔
103
            push {TransactionKernelField::Fee as u32}
81✔
104
            // _ *w [txkmh] [txkmh] txkm_height fee_leaf_index
81✔
105

81✔
106
            dup 12 {&block_body_field} {&body_field_kernel} {&kernel_field_fee}
81✔
107
            // _ *w [txkmh] [txkmh] txkm_height fee_leaf_index *fee
81✔
108

81✔
109
            addi {coin_size - 1} read_mem {coin_size} pop 1
81✔
110
            // _ *w [txkmh] [txkmh] txkm_height fee_leaf_index [fee]
81✔
111

81✔
112
            /* Using u128-lt here, guarantees fee is both positive and less
81✔
113
               than max allowed amount.
81✔
114
            */
81✔
115
            dup 3
81✔
116
            dup 3
81✔
117
            dup 3
81✔
118
            dup 3
81✔
119
            {&push_max_amount}
81✔
120
            call {u128_lt}
81✔
121
            push 0 eq
81✔
122
            // _ *w [txkmh] [txkmh] txkm_height fee_leaf_index [fee] (max >= fee)
81✔
123

81✔
124
            assert error_id {Self::ILLEGAL_FEE}
81✔
125
            // _ *w [txkmh] [txkmh] txkm_height fee_leaf_index [fee]
81✔
126

81✔
127
            call {hash_from_stack_amount}
81✔
128
            // _ *w [txkmh] [txkmh] txkm_height fee_leaf_index [fee_hash]
81✔
129

81✔
130
            call {merkle_verify}
81✔
131
            // _ *w [txkmh]
81✔
132
        );
81✔
133

81✔
134
        let hash_of_one = Tip5::hash(&1);
81✔
135
        let push_hash_of_one = hash_of_one
81✔
136
            .values()
81✔
137
            .into_iter()
81✔
138
            .rev()
81✔
139
            .map(|b| triton_instr!(push b))
405✔
140
            .collect_vec();
81✔
141
        let verify_set_merge_bit = triton_asm!(
81✔
142
            // _ [txkmh]
81✔
143

81✔
144
            dup 4
81✔
145
            dup 4
81✔
146
            dup 4
81✔
147
            dup 4
81✔
148
            dup 4
81✔
149
            push {TransactionKernel::MAST_HEIGHT}
81✔
150
            // _ [txkmh] [txkmh] txkm_height
81✔
151

81✔
152
            push {TransactionKernelField::MergeBit as u32}
81✔
153
            // _ [txkmh] [txkmh] txkm_height leaf_index
81✔
154

81✔
155
            {&push_hash_of_one}
81✔
156
            // _ [txkmh] [txkmh] txkm_height fee_leaf_index [merge_bit_hash (true)]
81✔
157

81✔
158
            call {merkle_verify}
81✔
159
            // _ [txkmh]
81✔
160
        );
81✔
161

81✔
162
        let authenticate_txkmh = triton_asm!(
81✔
163
            // _ [bbd] *w [txkmh]
81✔
164

81✔
165
            dup 4
81✔
166
            dup 4
81✔
167
            dup 4
81✔
168
            dup 4
81✔
169
            dup 4
81✔
170
            call {hash_from_stack_digest}
81✔
171
            // _ [bbd] *w [txkmh] [txkmh_hash]
81✔
172

81✔
173
            dup 15
81✔
174
            dup 15
81✔
175
            dup 15
81✔
176
            dup 15
81✔
177
            dup 15
81✔
178
            // _ [bbd] *w [txkmh] [txkmh_hash] [bbd]
81✔
179

81✔
180
            push {BlockBody::MAST_HEIGHT}
81✔
181
            push {BlockBodyField::TransactionKernel as u32}
81✔
182
            // _ [bbd] *w [txkmh] [txkmh_hash] [bbd] block_body_mast_height txk_leaf_index
81✔
183

81✔
184
            pick 11
81✔
185
            pick 11
81✔
186
            pick 11
81✔
187
            pick 11
81✔
188
            pick 11
81✔
189
            // _ [bbd] *w [txkmh] [bbd] block_body_mast_height txk_leaf_index [txkmh_hash]
81✔
190

81✔
191
            call {merkle_verify}
81✔
192
            // _ [bbd] *w [txkmh]
81✔
193
        );
81✔
194

81✔
195
        let hash_varlen = library.import(Box::new(HashVarlen));
81✔
196

81✔
197
        let verify_all_claims_loop = "verify_all_claims_loop".to_string();
81✔
198

81✔
199
        let verify_all_claims_function = triton_asm! {
81✔
200
            // INVARIANT: _ *claim[i]_si *proof[i]_si N i
81✔
201
            {verify_all_claims_loop}:
81✔
202

81✔
203
                // terminate if done
81✔
204
                dup 1 dup 1 eq skiz return
81✔
205

81✔
206
                pick 3
81✔
207
                // _ *proof[i]_si N i *claim[i]_si
81✔
208

81✔
209

81✔
210
                /* print claim hash */
81✔
211
                // _ *proof[i]_si N i *claim[i]_si
81✔
212

81✔
213
                read_mem 1
81✔
214
                addi 2
81✔
215
                // _ *proof[i]_si N i claim[i]_si *claim[i]
81✔
216

81✔
217
                dup 0
81✔
218
                dup 2
81✔
219
                call {hash_varlen}
81✔
220
                // _ *proof[i]_si N i claim[i]_si *claim[i] [hash(claim)]
81✔
221

81✔
222
                write_io {Digest::LEN}
81✔
223
                // _ *proof[i]_si N i claim[i]_si *claim[i]
81✔
224

81✔
225

81✔
226
                /* verify claim */
81✔
227
                dup 0
81✔
228
                dup 5
81✔
229
                addi 1
81✔
230
                // _ *proof[i]_si N i claim[i]_si *claim[i] *claim[i] *proof[i]
81✔
231

81✔
232
                call {stark_verify}
81✔
233
                // _ *proof[i]_si N i claim[i]_si *claim[i]
81✔
234

81✔
235

81✔
236
                /* Update pointers and counter */
81✔
237
                add
81✔
238
                // _ *proof[i]_si N i *claim[i+1]_si
81✔
239

81✔
240
                swap 3
81✔
241
                read_mem 1
81✔
242
                // _ *claim[i+1]_si N i proof[i]_si (*proof[i] - 2)
81✔
243

81✔
244
                push {MAX_PROOF_SIZE}
81✔
245
                dup 2
81✔
246
                lt
81✔
247
                assert error_id {Self::PROOF_SIZE_INDICATOR_TOO_BIG}
81✔
248

81✔
249
                addi 2
81✔
250
                add
81✔
251
                // _ *claim[i+1]_si N i *proof[i+1]_si
81✔
252

81✔
253
                place 2
81✔
254
                // _ *claim[i+1]_si *proof[i+1]_si N i
81✔
255

81✔
256
                addi 1
81✔
257
                // _ *claim[i+1]_si *proof[i+1]_si N (i + 1)
81✔
258

81✔
259
                recurse
81✔
260
        };
81✔
261

81✔
262
        let code = triton_asm! {
81✔
263
            // _
81✔
264

81✔
265
            read_io 5
81✔
266
            // _ [block_body_digest]
81✔
267

81✔
268
            push {FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS}
81✔
269
            hint block_witness_ptr = stack[0]
81✔
270
            // _ [bbd] *w
81✔
271

81✔
272
            divine {Digest::LEN}
81✔
273
            // _ [bbd] *w [txkmh]
81✔
274

81✔
275
            {&authenticate_txkmh}
81✔
276
            // _ [bbd] *w [txkmh]
81✔
277

81✔
278
            {&verify_fee_legality}
81✔
279
            // _ [bbd] *w [txkmh]
81✔
280

81✔
281
            {&verify_set_merge_bit}
81✔
282
            // _ [bbd] *w [txkmh]
81✔
283

81✔
284
            pop {Digest::LEN}
81✔
285
            // _ [bbd] *w
81✔
286

81✔
287
            /* verify appendix claims */
81✔
288
            dup 0 {&block_witness_field_claims}
81✔
289
            hint claims = stack[0]
81✔
290
            swap 1 {&block_witness_field_proofs}
81✔
291
            hint proofs = stack[1]
81✔
292
            // _ [bbd] *claims *proofs
81✔
293

81✔
294
            dup 1 read_mem 1 pop 1
81✔
295
            // _ [bbd] *claims *proofs N
81✔
296

81✔
297
            pick 2 addi 1
81✔
298
            pick 2 addi 1
81✔
299
            pick 2
81✔
300
            // _ [bbd] *claim[0] *proof[0] N
81✔
301

81✔
302
            push 0
81✔
303
            // _ [bbd] *claim[0] *proof[0] N 0
81✔
304

81✔
305
            call {verify_all_claims_loop}
81✔
306
            // _ [bbd] *claim[0] *proof[0] N N
81✔
307

81✔
308
            pop 4
81✔
309
            pop 5
81✔
310
            // _
81✔
311

81✔
312
            halt
81✔
313

81✔
314
            {&verify_all_claims_function}
81✔
315
            {&library.all_imports()}
81✔
316
        };
81✔
317

81✔
318
        (library, code)
81✔
319
    }
81✔
320

321
    fn hash(&self) -> Digest {
231✔
322
        static HASH: OnceLock<Digest> = OnceLock::new();
323

324
        *HASH.get_or_init(|| self.program().hash())
231✔
325
    }
231✔
326
}
327

328
#[cfg(test)]
329
pub(crate) mod test {
330
    use itertools::Itertools;
331
    use macro_rules_attr::apply;
332
    use rand::rngs::StdRng;
333
    use rand::Rng;
334
    use rand::SeedableRng;
335
    use tasm_lib::triton_vm;
336
    use tasm_lib::triton_vm::prelude::BFieldElement;
337
    use tasm_lib::triton_vm::prelude::Program;
338
    use tasm_lib::triton_vm::vm::NonDeterminism;
339
    use tasm_lib::triton_vm::vm::PublicInput;
340
    use tracing_test::traced_test;
341

342
    use super::*;
343
    use crate::config_models::cli_args;
344
    use crate::config_models::network::Network;
345
    use crate::job_queue::triton_vm::TritonVmJobPriority;
346
    use crate::job_queue::triton_vm::TritonVmJobQueue;
347
    use crate::mine_loop::create_block_transaction_from;
348
    use crate::mine_loop::TxMergeOrigin;
349
    use crate::models::blockchain::block::validity::block_primitive_witness::test::deterministic_block_primitive_witness;
350
    use crate::models::blockchain::block::Block;
351
    use crate::models::blockchain::block::TritonVmProofJobOptions;
352
    use crate::models::blockchain::transaction::Transaction;
353
    use crate::models::proof_abstractions::tasm::builtins as tasm;
354
    use crate::models::proof_abstractions::tasm::builtins::verify_stark;
355
    use crate::models::proof_abstractions::tasm::program::test::ConsensusProgramSpecification;
356
    use crate::models::proof_abstractions::timestamp::Timestamp;
357
    use crate::models::proof_abstractions::SecretWitness;
358
    use crate::models::state::tx_creation_config::TxCreationConfig;
359
    use crate::models::state::tx_proving_capability::TxProvingCapability;
360
    use crate::models::state::wallet::transaction_output::TxOutput;
361
    use crate::models::state::wallet::wallet_entropy::WalletEntropy;
362
    use crate::tests::shared::mock_genesis_global_state;
363
    use crate::tests::shared_tokio_runtime;
364
    use crate::GlobalStateLock;
365

366
    impl ConsensusProgramSpecification for BlockProgram {
367
        fn source(&self) {
2✔
368
            let block_body_digest: Digest = tasm::tasmlib_io_read_stdin___digest();
2✔
369
            let start_address: BFieldElement =
2✔
370
                FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS;
2✔
371
            let block_witness: BlockProofWitness = tasm::decode_from_memory(start_address);
2✔
372
            let claims: Vec<Claim> = block_witness.claims;
2✔
373
            let proofs: Vec<Proof> = block_witness.proofs;
2✔
374

2✔
375
            let block_body = &block_witness.block_body;
2✔
376

2✔
377
            let txk_mast_hash: Digest = tasm::tasmlib_io_read_secin___digest();
2✔
378
            let txk_mast_hash_as_leaf = Tip5::hash(&txk_mast_hash);
2✔
379
            tasm::tasmlib_hashing_merkle_verify(
2✔
380
                block_body_digest,
2✔
381
                BlockBodyField::TransactionKernel as u32,
2✔
382
                txk_mast_hash_as_leaf,
2✔
383
                BlockBody::MAST_HEIGHT as u32,
2✔
384
            );
2✔
385

2✔
386
            // Verify fee is legal
2✔
387
            let fee = &block_body.transaction_kernel.fee;
2✔
388
            let fee_hash = Tip5::hash(fee);
2✔
389
            tasm::tasmlib_hashing_merkle_verify(
2✔
390
                txk_mast_hash,
2✔
391
                TransactionKernelField::Fee as u32,
2✔
392
                fee_hash,
2✔
393
                TransactionKernel::MAST_HEIGHT as u32,
2✔
394
            );
2✔
395

2✔
396
            assert!(!fee.is_negative());
2✔
397
            assert!(*fee <= NativeCurrencyAmount::max());
2✔
398

399
            // Verify that merge bit is set
400
            let merge_bit_hash = Tip5::hash(&1);
2✔
401
            tasm::tasmlib_hashing_merkle_verify(
2✔
402
                txk_mast_hash,
2✔
403
                TransactionKernelField::MergeBit as u32,
2✔
404
                merge_bit_hash,
2✔
405
                TransactionKernel::MAST_HEIGHT as u32,
2✔
406
            );
2✔
407

2✔
408
            let mut i = 0;
2✔
409
            while i < claims.len() {
5✔
410
                tasm::tasmlib_io_write_to_stdout___digest(Tip5::hash(&claims[i]));
3✔
411
                verify_stark(Stark::default(), &claims[i], &proofs[i]);
3✔
412

3✔
413
                i += 1;
3✔
414
            }
3✔
415
        }
2✔
416
    }
417

UNCOV
418
    #[traced_test]
×
419
    #[test]
420
    fn block_program_halts_gracefully() {
1✔
421
        let block_primitive_witness = deterministic_block_primitive_witness();
1✔
422
        let block_body_mast_hash_as_input = PublicInput::new(
1✔
423
            block_primitive_witness
1✔
424
                .body()
1✔
425
                .mast_hash()
1✔
426
                .reversed()
1✔
427
                .values()
1✔
428
                .to_vec(),
1✔
429
        );
1✔
430

1✔
431
        let block_proof_witness = BlockProofWitness::produce(block_primitive_witness);
1✔
432

1✔
433
        let block_program_nondeterminism = block_proof_witness.nondeterminism();
1✔
434
        let rust_output = BlockProgram
1✔
435
            .run_rust(
1✔
436
                &block_body_mast_hash_as_input,
1✔
437
                block_program_nondeterminism.clone(),
1✔
438
            )
1✔
439
            .unwrap();
1✔
440
        let tasm_output = match BlockProgram
1✔
441
            .run_tasm(&block_body_mast_hash_as_input, block_program_nondeterminism)
1✔
442
        {
443
            Ok(std_out) => std_out,
1✔
UNCOV
444
            Err(err) => panic!("{err:?}"),
×
445
        };
446

447
        assert_eq!(rust_output, tasm_output);
1✔
448

449
        let expected_output = block_proof_witness
1✔
450
            .claims()
1✔
451
            .iter()
1✔
452
            .flat_map(|appendix_claim| Tip5::hash(appendix_claim).values().to_vec())
1✔
453
            .collect_vec();
1✔
454
        assert_eq!(
1✔
455
            expected_output, tasm_output,
UNCOV
456
            "tasm output must equal rust output"
×
457
        );
458
    }
1✔
459

460
    // TODO: Add test that verifies that double spends *within* one block is
461
    //       disallowed.
462

UNCOV
463
    #[traced_test]
×
464
    #[apply(shared_tokio_runtime)]
465
    async fn disallow_double_spends_across_blocks() {
466
        async fn mine_tx(
2✔
467
            state: &GlobalStateLock,
2✔
468
            tx: Transaction,
2✔
469
            predecessor: &Block,
2✔
470
            timestamp: Timestamp,
2✔
471
        ) -> Block {
2✔
472
            let (block_tx, _) = create_block_transaction_from(
2✔
473
                predecessor,
2✔
474
                state,
2✔
475
                timestamp,
2✔
476
                TritonVmProofJobOptions::default(),
2✔
477
                TxMergeOrigin::ExplicitList(vec![tx]),
2✔
478
            )
2✔
479
            .await
2✔
480
            .unwrap();
2✔
481

2✔
482
            Block::compose(
2✔
483
                predecessor,
2✔
484
                block_tx,
2✔
485
                timestamp,
2✔
486
                None,
2✔
487
                TritonVmJobQueue::get_instance(),
2✔
488
                TritonVmProofJobOptions::default(),
2✔
489
            )
2✔
490
            .await
2✔
491
            .unwrap()
2✔
492
        }
2✔
493

494
        let network = Network::Main;
495
        let mut rng: StdRng = SeedableRng::seed_from_u64(2225550001);
496
        let alice_wallet = WalletEntropy::devnet_wallet();
497
        let alice = mock_genesis_global_state(
498
            network,
499
            3,
500
            WalletEntropy::devnet_wallet(),
501
            cli_args::Args::default(),
502
        )
503
        .await;
504

505
        let alice_key = alice_wallet.nth_generation_spending_key_for_tests(0);
506
        let fee = NativeCurrencyAmount::coins(1);
507
        let tx_output = TxOutput::offchain_native_currency(
508
            NativeCurrencyAmount::coins(1),
509
            rng.random(),
510
            alice_key.to_address().into(),
511
            false,
512
        );
513

514
        let genesis_block = Block::genesis(network);
515
        let now = genesis_block.header().timestamp + Timestamp::months(12);
516
        let config = TxCreationConfig::default()
517
            .recover_change_off_chain(alice_key.into())
518
            .with_prover_capability(TxProvingCapability::SingleProof);
519
        let tx: Transaction = alice
520
            .api()
521
            .tx_initiator_internal()
522
            .create_transaction(vec![tx_output].into(), fee, now, config)
523
            .await
524
            .unwrap()
525
            .transaction
526
            .into();
527
        let block1 = mine_tx(&alice, tx.clone(), &genesis_block, now).await;
528

529
        // Update transaction, stick it into block 2, and verify that block 2
530
        // is invalid.
531
        let later = now + Timestamp::months(1);
532
        let tx = Transaction::new_with_updated_mutator_set_records_given_proof(
533
            tx.kernel,
534
            &genesis_block.mutator_set_accumulator_after(),
535
            &block1.mutator_set_update(),
536
            tx.proof.into_single_proof(),
537
            TritonVmJobQueue::get_instance(),
538
            TritonVmJobPriority::default().into(),
539
            Some(later),
540
        )
541
        .await
542
        .unwrap();
543

544
        let block2 = mine_tx(&alice, tx, &block1, later).await;
545
        assert!(
546
            !block2.is_valid(&block1, later, network).await,
547
            "Block doing a double-spend must be invalid."
548
        );
549
    }
550

551
    #[test]
552
    fn can_verify_block_program_with_two_claims() {
1✔
553
        let block_primitive_witness = deterministic_block_primitive_witness();
1✔
554
        let block_body_mast_hash_as_input = PublicInput::new(
1✔
555
            block_primitive_witness
1✔
556
                .body()
1✔
557
                .mast_hash()
1✔
558
                .reversed()
1✔
559
                .values()
1✔
560
                .to_vec(),
1✔
561
        );
1✔
562

1✔
563
        let halt_program = Program::new(&triton_asm!(halt));
1✔
564
        let halt_claim = Claim::new(halt_program.hash());
1✔
565
        let halt_nondeterminism = NonDeterminism::default();
1✔
566
        let halt_proof = triton_vm::prove(
1✔
567
            Stark::default(),
1✔
568
            &halt_claim,
1✔
569
            halt_program,
1✔
570
            halt_nondeterminism,
1✔
571
        )
1✔
572
        .unwrap();
1✔
573

1✔
574
        let block_proof_witness = BlockProofWitness::produce(block_primitive_witness)
1✔
575
            .with_claim_test(halt_claim, halt_proof.into());
1✔
576

1✔
577
        let block_program_nondeterminism = block_proof_witness.nondeterminism();
1✔
578
        let rust_output = BlockProgram
1✔
579
            .run_rust(
1✔
580
                &block_body_mast_hash_as_input,
1✔
581
                block_program_nondeterminism.clone(),
1✔
582
            )
1✔
583
            .unwrap();
1✔
584
        let tasm_output = match BlockProgram
1✔
585
            .run_tasm(&block_body_mast_hash_as_input, block_program_nondeterminism)
1✔
586
        {
587
            Ok(std_out) => std_out,
1✔
UNCOV
588
            Err(err) => panic!("{err:?}"),
×
589
        };
590

591
        assert_eq!(rust_output, tasm_output);
1✔
592

593
        let expected_output = block_proof_witness
1✔
594
            .claims()
1✔
595
            .iter()
1✔
596
            .flat_map(|appendix_claim| Tip5::hash(appendix_claim).values().to_vec())
2✔
597
            .collect_vec();
1✔
598
        assert_eq!(
1✔
599
            expected_output, tasm_output,
UNCOV
600
            "tasm output must equal rust output"
×
601
        );
602
    }
1✔
603
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc