Skip to content

Commit

Permalink
Add KIP-10 Transaction Introspection Opcodes, 8-byte arithmetic and H…
Browse files Browse the repository at this point in the history
…ard Fork Support (#487)

* implement new opcodes

* example of mutual tx

* add docs describing scenario

* introduce feature gate for new features

* introduce hf feature that enables txscript hf feature

* style: fmt and clippy fix

* implement new opcodes

* example of mutual tx

* add docs describing scenario

* introduce feature gate for new features

* style: fmt and clippy fix

* remove unused feature

* fmt

* make opcode invalid in case of feature disabled

* feature gate test

* change test set based on feature
add ci cd test

* rename InputSPK -> InputSpk

* enable kip10 opcodes based on daa_score in runtime

* use dummy kip10 activation daa score in params

* use dummy kip10 activation daa score in params

* suppress clippy lint

* add example with shared key

* fix clippy

* remove useless check from example

* add one-time borrowing example

* Implement one-time and two-times threshold borrowing scenarios

- Add threshold_scenario_limited_one_time function
- Add threshold_scenario_limited_2_times function
- Create generate_limited_time_script for reusable script generation
- Implement nested script structure for two-times borrowing
- Update documentation for both scenarios
- Add tests for owner spending, borrowing, and invalid attempts in both cases
- Ensure consistent error handling and logging across scenarios
- Refactor to use more generic script generation approach

* fix: fix incorrect sig-op count

* correct error description

* style: fmt

* pass kip-10 flag in constructor params

* remove borrow scenario from tests.
run tests against both kip1- enabled/disabled engine

* introduce method that converts spk to bytes.
add tests covering new opcodes

* return comment describing where invalid opcodes starts from.
add comments describing why 2 files are used.

* fix wring error messages

* support introspection by index

* test input spk

* test output spk

* tests refactor

* support 8-byte arithmetics

* Standartize fork activation logic (#588)

* Use ForkActivation for all fork activations

* Avoid using negation in some ifs

* Add is_within_range_from_activation

* Move 'is always' check inside is_within_range_from_activation

* lints

* Refactoring for cleaner pruning proof module (#589)

* Cleanup manual block level calc

There were two areas in pruning proof mod that
manually calculated block level.

This replaces those with a call to calc_block_level

* Refactor pruning proof build functions

* Refactor apply pruning proof functions

* Refactor validate pruning functions

* Add comments for clarity

* only enable 8 byte arithmetics for kip10

* use i64 value in 9-byte tests

* fix tests covering kip10 and i64 deserialization

* fix test according to 8-byte math

* finish test covering kip10 opcodes: input/output/amount/spk

* fix kip10 examples

* rename test

* feat: add input index op

* feat: add input/outpiut opcodes

* reseve opcodes
reorder kip10 opcodes.
reflect script tests

* fix example

* introspection opcodes are reserverd, not disables

* use ForkActivation type

* cicd: run kip-10 example

* move spk encoding to txscript module

* rework bound check ot input/output index

* fix tests by importing spkencoding trait

* replace todo in descripotions of over[under]flow errors

* reorder new opcodes, reserve script sig opcode, remove txid

* fix bitcoin script tests

* add simple opcode tests

* rename id(which represents input index) to idx

* fix comments

* add input spk tests

* refactor test cases

* refactor(txscript): Enforce input index invariant via assertion

Change TxScriptEngine::from_transaction_input to assert valid input index
instead of returning Result. This better reflects that an invalid index is a
caller's (transaction validation) error rather than a script engine error,
since the input must be part of the transaction being validated.

An invalid index signifies a mismatch between the transaction and the input being
validated - this is a programming error in the transaction validator layer, not
a script engine concern. The script engine should be able to assume it receives
valid inputs from its caller.

The change simplifies error handling by enforcing this invariant early,
while maintaining identical behavior for valid inputs. The function is
now documented to panic on malformed inputs.

This is a breaking change for code that previously handled
InvalidIndex errors, though such handling was likely incorrect
as it indicated an inconsistency in transaction validation.

* refactor error types to contain correct info

* rename id to idx

* rename opcode

* make construction of TxScriptEngine from transaction input infallible

* style: format combinators chain

* add integration test covering activation of kip10

* rename kip10_activation_daa_score to kip10_activation

* Update crypto/txscript/src/lib.rs

refactor vector filling

* rework assert

* verify that block is disqualified in case of it has tx which requires
block that contains the tx with kip10 opcode is accepted after daa score has being reached

* revert changer to infallible api

* add doc comments

* Update crypto/txscript/src/opcodes/mod.rs

Fallible conversion of output amount (usize -> i64)

* Update crypto/txscript/src/opcodes/mod.rs

Fallible conversion of input amount (usize -> i64)

* add required import

* refactor: SigHashReusedValuesUnsync doesnt neet to be mutable

* fix test description

* rework example

* 9 byte integers must fail to serialize

* add todo

* rewrite todo

* remove redundant code

* remove redundant mut in example

* remove redundant mut in example

* remove redundant mut in example

* cicd: apply lint to examples

---------

Co-authored-by: Ori Newman <[email protected]>
  • Loading branch information
biryukovmaxim and someone235 authored Nov 12, 2024
1 parent b516ac6 commit 8b3ed07
Show file tree
Hide file tree
Showing 21 changed files with 7,700 additions and 271 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ jobs:

- name: Run cargo doc
run: cargo doc --release --no-deps --workspace
- name: Run kip-10 example
run: cargo run --example kip-10


# test-release:
# name: Test Suite Release
Expand Down Expand Up @@ -210,7 +213,7 @@ jobs:
run: cargo fmt --all -- --check

- name: Run cargo clippy
run: cargo clippy --workspace --tests --benches -- -D warnings
run: cargo clippy --workspace --tests --benches --examples -- -D warnings


check-wasm32:
Expand Down
7 changes: 4 additions & 3 deletions consensus/benches/check_scripts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ fn benchmark_check_scripts(c: &mut Criterion) {
let cache = Cache::new(inputs_count as u64);
b.iter(|| {
cache.clear();
check_scripts_sequential(black_box(&cache), black_box(&tx.as_verifiable())).unwrap();
check_scripts_sequential(black_box(&cache), black_box(&tx.as_verifiable()), false).unwrap();
})
});

Expand All @@ -93,7 +93,7 @@ fn benchmark_check_scripts(c: &mut Criterion) {
let cache = Cache::new(inputs_count as u64);
b.iter(|| {
cache.clear();
check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable())).unwrap();
check_scripts_par_iter(black_box(&cache), black_box(&tx.as_verifiable()), false).unwrap();
})
});

Expand All @@ -107,7 +107,8 @@ fn benchmark_check_scripts(c: &mut Criterion) {
let cache = Cache::new(inputs_count as u64);
b.iter(|| {
cache.clear();
check_scripts_par_iter_pool(black_box(&cache), black_box(&tx.as_verifiable()), black_box(&pool)).unwrap();
check_scripts_par_iter_pool(black_box(&cache), black_box(&tx.as_verifiable()), black_box(&pool), false)
.unwrap();
})
});
}
Expand Down
2 changes: 1 addition & 1 deletion consensus/client/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ pub fn calc_schnorr_signature_hash(
let utxo = cctx::UtxoEntry::from(utxo.as_ref());

let hash_type = SIG_HASH_ALL;
let mut reused_values = SigHashReusedValues::new();
let reused_values = SigHashReusedValuesUnsync::new();

// let input = verifiable_tx.populated_input(input_index);
// let tx = verifiable_tx.tx();
Expand Down
18 changes: 17 additions & 1 deletion consensus/core/src/config/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ pub struct Params {
/// DAA score from which storage mass calculation and transaction mass field are activated as a consensus rule
pub storage_mass_activation: ForkActivation,

/// DAA score from which tx engine:
/// 1. Supports 8-byte integer arithmetic operations (previously limited to 4 bytes)
/// 2. Supports transaction introspection opcodes:
/// - OpTxInputCount (0xb3): Get number of inputs
/// - OpTxOutputCount (0xb4): Get number of outputs
/// - OpTxInputIndex (0xb9): Get current input index
/// - OpTxInputAmount (0xbe): Get input amount
/// - OpTxInputSpk (0xbf): Get input script public key
/// - OpTxOutputAmount (0xc2): Get output amount
/// - OpTxOutputSpk (0xc3): Get output script public key
pub kip10_activation: ForkActivation,

/// DAA score after which the pre-deflationary period switches to the deflationary period
pub deflationary_phase_daa_score: u64,

Expand Down Expand Up @@ -380,6 +392,7 @@ pub const MAINNET_PARAMS: Params = Params {

storage_mass_parameter: STORAGE_MASS_PARAMETER,
storage_mass_activation: ForkActivation::never(),
kip10_activation: ForkActivation::never(),

// deflationary_phase_daa_score is the DAA score after which the pre-deflationary period
// switches to the deflationary period. This number is calculated as follows:
Expand Down Expand Up @@ -443,7 +456,7 @@ pub const TESTNET_PARAMS: Params = Params {

storage_mass_parameter: STORAGE_MASS_PARAMETER,
storage_mass_activation: ForkActivation::never(),

kip10_activation: ForkActivation::never(),
// deflationary_phase_daa_score is the DAA score after which the pre-deflationary period
// switches to the deflationary period. This number is calculated as follows:
// We define a year as 365.25 days
Expand Down Expand Up @@ -513,6 +526,7 @@ pub const TESTNET11_PARAMS: Params = Params {

storage_mass_parameter: STORAGE_MASS_PARAMETER,
storage_mass_activation: ForkActivation::always(),
kip10_activation: ForkActivation::never(),

skip_proof_of_work: false,
max_block_level: 250,
Expand Down Expand Up @@ -566,6 +580,7 @@ pub const SIMNET_PARAMS: Params = Params {

storage_mass_parameter: STORAGE_MASS_PARAMETER,
storage_mass_activation: ForkActivation::always(),
kip10_activation: ForkActivation::never(),

skip_proof_of_work: true, // For simnet only, PoW can be simulated by default
max_block_level: 250,
Expand Down Expand Up @@ -612,6 +627,7 @@ pub const DEVNET_PARAMS: Params = Params {

storage_mass_parameter: STORAGE_MASS_PARAMETER,
storage_mass_activation: ForkActivation::never(),
kip10_activation: ForkActivation::never(),

// deflationary_phase_daa_score is the DAA score after which the pre-deflationary period
// switches to the deflationary period. This number is calculated as follows:
Expand Down
14 changes: 14 additions & 0 deletions consensus/core/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ pub trait VerifiableTransaction {
fn id(&self) -> TransactionId {
self.tx().id()
}

fn utxo(&self, index: usize) -> Option<&UtxoEntry>;
}

/// A custom iterator written only so that `populated_inputs` has a known return type and can de defined on the trait level
Expand Down Expand Up @@ -342,6 +344,10 @@ impl<'a> VerifiableTransaction for PopulatedTransaction<'a> {
fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry) {
(&self.tx.inputs[index], &self.entries[index])
}

fn utxo(&self, index: usize) -> Option<&UtxoEntry> {
self.entries.get(index)
}
}

/// Represents a validated transaction with populated UTXO entry data and a calculated fee
Expand Down Expand Up @@ -370,6 +376,10 @@ impl<'a> VerifiableTransaction for ValidatedTransaction<'a> {
fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry) {
(&self.tx.inputs[index], &self.entries[index])
}

fn utxo(&self, index: usize) -> Option<&UtxoEntry> {
self.entries.get(index)
}
}

impl AsRef<Transaction> for Transaction {
Expand Down Expand Up @@ -507,6 +517,10 @@ impl<T: AsRef<Transaction>> VerifiableTransaction for MutableTransactionVerifiab
self.inner.entries[index].as_ref().expect("expected to be called only following full UTXO population"),
)
}

fn utxo(&self, index: usize) -> Option<&UtxoEntry> {
self.inner.entries.get(index).and_then(Option::as_ref)
}
}

/// Specialized impl for `T=Arc<Transaction>`
Expand Down
1 change: 1 addition & 0 deletions consensus/src/consensus/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ impl ConsensusServices {
tx_script_cache_counters,
mass_calculator.clone(),
params.storage_mass_activation,
params.kip10_activation,
);

let pruning_point_manager = PruningPointManager::new(
Expand Down
26 changes: 18 additions & 8 deletions consensus/src/consensus/test_consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ use kaspa_hashes::Hash;
use kaspa_notify::subscription::context::SubscriptionContext;
use parking_lot::RwLock;

use kaspa_database::create_temp_db;
use kaspa_database::prelude::ConnBuilder;
use std::future::Future;
use std::{sync::Arc, thread::JoinHandle};

use super::services::{DbDagTraversalManager, DbGhostdagManager, DbWindowManager};
use super::Consensus;
use crate::pipeline::virtual_processor::test_block_builder::TestBlockBuilder;
use crate::processes::window::WindowManager;
use crate::{
Expand All @@ -35,9 +32,10 @@ use crate::{
pipeline::{body_processor::BlockBodyProcessor, virtual_processor::VirtualStateProcessor, ProcessingCounters},
test_helpers::header_from_precomputed_hash,
};

use super::services::{DbDagTraversalManager, DbGhostdagManager, DbWindowManager};
use super::Consensus;
use kaspa_database::create_temp_db;
use kaspa_database::prelude::ConnBuilder;
use std::future::Future;
use std::{sync::Arc, thread::JoinHandle};

pub struct TestConsensus {
params: Params,
Expand Down Expand Up @@ -138,6 +136,12 @@ impl TestConsensus {
self.validate_and_insert_block(self.build_block_with_parents(hash, parents).to_immutable()).virtual_state_task
}

/// Adds a valid block with the given transactions and parents to the consensus.
///
/// # Panics
///
/// Panics if block builder validation rules are violated.
/// See `kaspa_consensus_core::errors::block::RuleError` for the complete list of possible validation rules.
pub fn add_utxo_valid_block_with_parents(
&self,
hash: Hash,
Expand All @@ -149,6 +153,12 @@ impl TestConsensus {
.virtual_state_task
}

/// Builds a valid block with the given transactions, parents, and miner data.
///
/// # Panics
///
/// Panics if block builder validation rules are violated.
/// See `kaspa_consensus_core::errors::block::RuleError` for the complete list of possible validation rules.
pub fn build_utxo_valid_block_with_parents(
&self,
hash: Hash,
Expand Down
6 changes: 6 additions & 0 deletions consensus/src/processes/transaction_validator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ pub struct TransactionValidator {

/// Storage mass hardfork DAA score
storage_mass_activation: ForkActivation,
/// KIP-10 hardfork DAA score
kip10_activation: ForkActivation,
}

impl TransactionValidator {
#[allow(clippy::too_many_arguments)]
pub fn new(
max_tx_inputs: usize,
max_tx_outputs: usize,
Expand All @@ -42,6 +45,7 @@ impl TransactionValidator {
counters: Arc<TxScriptCacheCounters>,
mass_calculator: MassCalculator,
storage_mass_activation: ForkActivation,
kip10_activation: ForkActivation,
) -> Self {
Self {
max_tx_inputs,
Expand All @@ -54,6 +58,7 @@ impl TransactionValidator {
sig_cache: Cache::with_counters(10_000, counters),
mass_calculator,
storage_mass_activation,
kip10_activation,
}
}

Expand All @@ -78,6 +83,7 @@ impl TransactionValidator {
sig_cache: Cache::with_counters(10_000, counters),
mass_calculator: MassCalculator::new(0, 0, 0, 0),
storage_mass_activation: ForkActivation::never(),
kip10_activation: ForkActivation::never(),
}
}
}
Loading

0 comments on commit 8b3ed07

Please sign in to comment.