mirror of
https://github.com/hinto-janai/cuprate.git
synced 2024-12-22 11:39:30 +00:00
link transaction rules to monero-book
This commit is contained in:
parent
13957a5e7f
commit
730bc8fb42
7 changed files with 209 additions and 91 deletions
|
@ -15,6 +15,10 @@ const BLOCK_FUTURE_TIME_LIMIT: u64 = 60 * 60 * 2;
|
|||
const BLOCK_202612_POW_HASH: [u8; 32] =
|
||||
hex_literal::hex!("84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000");
|
||||
|
||||
pub const PENALTY_FREE_ZONE_1: usize = 20000;
|
||||
pub const PENALTY_FREE_ZONE_2: usize = 60000;
|
||||
pub const PENALTY_FREE_ZONE_5: usize = 300000;
|
||||
|
||||
const RX_SEEDHASH_EPOCH_BLOCKS: u64 = 2048;
|
||||
const RX_SEEDHASH_EPOCH_LAG: u64 = 64;
|
||||
|
||||
|
@ -109,6 +113,19 @@ pub fn check_block_pow(hash: &[u8; 32], difficulty: u128) -> Result<(), BlockErr
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the penalty free zone
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html#penalty-free-zone
|
||||
pub fn penalty_free_zone(hf: &HardFork) -> usize {
|
||||
if hf == &HardFork::V1 {
|
||||
PENALTY_FREE_ZONE_1
|
||||
} else if hf >= &HardFork::V2 && hf < &HardFork::V5 {
|
||||
PENALTY_FREE_ZONE_2
|
||||
} else {
|
||||
PENALTY_FREE_ZONE_5
|
||||
}
|
||||
}
|
||||
|
||||
/// Sanity check on the block blob size.
|
||||
///
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#block-weight-and-size
|
||||
|
@ -177,7 +194,7 @@ fn check_timestamp(block: &Block, median_timestamp: u64) -> Result<(), BlockErro
|
|||
/// ref: https://monero-book.cuprate.org/consensus_rules/blocks.html#no-duplicate-transactions
|
||||
fn check_txs_unique(txs: &[[u8; 32]]) -> Result<(), BlockError> {
|
||||
txs.windows(2).try_for_each(|window| {
|
||||
if window[0] != window[1] {
|
||||
if window[0] == window[1] {
|
||||
Err(BlockError::DuplicateTransaction)?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
use monero_serai::ringct::RctType;
|
||||
use std::{cmp::Ordering, collections::HashSet, sync::Arc};
|
||||
|
||||
use monero_serai::transaction::{Input, Output, Timelock, Transaction};
|
||||
use multiexp::BatchVerifier;
|
||||
|
||||
use crate::{check_point_canonically_encoded, is_decomposed_amount, HardFork};
|
||||
use crate::{
|
||||
blocks::penalty_free_zone, check_point_canonically_encoded, is_decomposed_amount, HardFork,
|
||||
};
|
||||
|
||||
mod contextual_data;
|
||||
mod ring_ct;
|
||||
|
@ -12,10 +15,15 @@ mod ring_signatures;
|
|||
pub use contextual_data::*;
|
||||
pub use ring_ct::RingCTError;
|
||||
|
||||
const MAX_BULLETPROOFS_OUTPUTS: usize = 16;
|
||||
const MAX_TX_BLOB_SIZE: usize = 1_000_000;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum TransactionError {
|
||||
#[error("The transactions version is incorrect.")]
|
||||
TransactionVersionInvalid,
|
||||
#[error("The transactions is too big.")]
|
||||
TooBig,
|
||||
//-------------------------------------------------------- OUTPUTS
|
||||
#[error("Output is not a valid point.")]
|
||||
OutputNotValidPoint,
|
||||
|
@ -23,12 +31,16 @@ pub enum TransactionError {
|
|||
OutputTypeInvalid,
|
||||
#[error("The transaction is v1 with a 0 amount output.")]
|
||||
ZeroOutputForV1,
|
||||
#[error("The transaction is v2 with a non 0 amount output.")]
|
||||
NonZeroOutputForV2,
|
||||
#[error("The transaction has an output which is not decomposed.")]
|
||||
AmountNotDecomposed,
|
||||
#[error("The transactions outputs overflow.")]
|
||||
OutputsOverflow,
|
||||
#[error("The transactions outputs too much.")]
|
||||
OutputsTooHigh,
|
||||
#[error("The transactions has too many outputs.")]
|
||||
InvalidNumberOfOutputs,
|
||||
//-------------------------------------------------------- INPUTS
|
||||
#[error("One or more inputs don't have the expected number of decoys.")]
|
||||
InputDoesNotHaveExpectedNumbDecoys,
|
||||
|
@ -57,16 +69,25 @@ pub enum TransactionError {
|
|||
RingSignatureIncorrect,
|
||||
//-------------------------------------------------------- RingCT
|
||||
#[error("RingCT Error: {0}.")]
|
||||
RingCTError(#[from] ring_ct::RingCTError),
|
||||
RingCTError(#[from] RingCTError),
|
||||
}
|
||||
|
||||
/// An enum representing all valid Monero transaction versions.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum TxVersion {
|
||||
/// Legacy ring signatures.
|
||||
RingSignatures,
|
||||
/// RingCT
|
||||
RingCT,
|
||||
}
|
||||
|
||||
impl TxVersion {
|
||||
/// Converts a `raw` version value to a [`TxVersion`].
|
||||
///
|
||||
/// This will return `None` on invalid values.
|
||||
///
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions.html#version
|
||||
/// && https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#version
|
||||
pub fn from_raw(version: u64) -> Option<TxVersion> {
|
||||
Some(match version {
|
||||
1 => TxVersion::RingSignatures,
|
||||
|
@ -80,7 +101,7 @@ impl TxVersion {
|
|||
|
||||
/// Checks the output keys are canonically encoded points.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#output-keys-canonical
|
||||
/// https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-keys-canonical
|
||||
fn check_output_keys(outputs: &[Output]) -> Result<(), TransactionError> {
|
||||
for out in outputs {
|
||||
if !check_point_canonically_encoded(&out.key) {
|
||||
|
@ -91,12 +112,12 @@ fn check_output_keys(outputs: &[Output]) -> Result<(), TransactionError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks the output types are allowed.
|
||||
/// Checks the output types are allowed for the given hard-fork.
|
||||
///
|
||||
/// This is also used during miner-tx verification.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#output-type
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/miner_tx.html#output-type
|
||||
/// https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-type
|
||||
/// https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#output-type
|
||||
pub(crate) fn check_output_types(
|
||||
outputs: &[Output],
|
||||
hf: &HardFork,
|
||||
|
@ -120,9 +141,9 @@ pub(crate) fn check_output_types(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks the outputs amount for version 1 txs.
|
||||
/// Checks the individual outputs amount for version 1 txs.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#output-amount
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-amount
|
||||
fn check_output_amount_v1(amount: u64, hf: &HardFork) -> Result<(), TransactionError> {
|
||||
if amount == 0 {
|
||||
return Err(TransactionError::ZeroOutputForV1);
|
||||
|
@ -135,20 +156,35 @@ fn check_output_amount_v1(amount: u64, hf: &HardFork) -> Result<(), TransactionE
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks the individual outputs amount for version 2 txs.
|
||||
///
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-amount
|
||||
fn check_output_amount_v2(amount: u64) -> Result<(), TransactionError> {
|
||||
if amount == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TransactionError::NonZeroOutputForV2)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sums the outputs, checking for overflow and other consensus rules.
|
||||
///
|
||||
/// Should only be used on v1 transactions.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#inputs-and-outputs-must-not-overflow
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#output-amount
|
||||
fn sum_outputs_v1(outputs: &[Output], hf: &HardFork) -> Result<u64, TransactionError> {
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#output-amount
|
||||
/// && https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#outputs-must-not-overflow
|
||||
fn sum_outputs(
|
||||
outputs: &[Output],
|
||||
hf: &HardFork,
|
||||
tx_version: &TxVersion,
|
||||
) -> Result<u64, TransactionError> {
|
||||
let mut sum: u64 = 0;
|
||||
|
||||
for out in outputs {
|
||||
let raw_amount = out.amount.unwrap_or(0);
|
||||
|
||||
check_output_amount_v1(raw_amount, hf)?;
|
||||
|
||||
match tx_version {
|
||||
TxVersion::RingSignatures => check_output_amount_v1(raw_amount, hf)?,
|
||||
TxVersion::RingCT => check_output_amount_v2(raw_amount)?,
|
||||
}
|
||||
sum = sum
|
||||
.checked_add(raw_amount)
|
||||
.ok_or(TransactionError::OutputsOverflow)?;
|
||||
|
@ -157,26 +193,60 @@ fn sum_outputs_v1(outputs: &[Output], hf: &HardFork) -> Result<u64, TransactionE
|
|||
Ok(sum)
|
||||
}
|
||||
|
||||
/// Checks the number of outputs is allowed.
|
||||
///
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html#2-outputs
|
||||
/// && https://monero-book.cuprate.org/consensus_rules/transactions/ring_ct/bulletproofs.html#max-outputs
|
||||
/// && https://monero-book.cuprate.org/consensus_rules/transactions/ring_ct/bulletproofs+.html#max-outputs
|
||||
fn check_number_of_outputs(
|
||||
outputs: usize,
|
||||
hf: &HardFork,
|
||||
tx_version: &TxVersion,
|
||||
rct_type: &RctType,
|
||||
) -> Result<(), TransactionError> {
|
||||
if tx_version == &TxVersion::RingSignatures {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if hf >= &HardFork::V12 && outputs < 2 {
|
||||
return Err(TransactionError::InvalidNumberOfOutputs);
|
||||
}
|
||||
|
||||
match rct_type {
|
||||
RctType::Bulletproofs | RctType::BulletproofsCompactAmount | RctType::BulletproofsPlus => {
|
||||
if outputs <= MAX_BULLETPROOFS_OUTPUTS {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TransactionError::InvalidNumberOfOutputs)
|
||||
}
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks the outputs against all output consensus rules, returning the sum of the output amounts.
|
||||
///
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/outputs.html
|
||||
/// && https://monero-book.cuprate.org/consensus_rules/transactions/ring_ct/bulletproofs.html#max-outputs
|
||||
/// && https://monero-book.cuprate.org/consensus_rules/transactions/ring_ct/bulletproofs+.html#max-outputs
|
||||
fn check_outputs_semantics(
|
||||
outputs: &[Output],
|
||||
hf: &HardFork,
|
||||
tx_version: &TxVersion,
|
||||
rct_type: &RctType,
|
||||
) -> Result<u64, TransactionError> {
|
||||
check_output_types(outputs, hf)?;
|
||||
check_output_keys(outputs)?;
|
||||
check_number_of_outputs(outputs.len(), hf, tx_version, rct_type)?;
|
||||
|
||||
match tx_version {
|
||||
TxVersion::RingSignatures => sum_outputs_v1(outputs, hf),
|
||||
TxVersion::RingCT => Ok(0), // RCT outputs are checked to be zero in RCT checks.
|
||||
}
|
||||
sum_outputs(outputs, hf, tx_version)
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------- TIME LOCKS
|
||||
|
||||
/// Checks if an outputs unlock time has passed.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time
|
||||
/// https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html
|
||||
fn output_unlocked(
|
||||
time_lock: &Timelock,
|
||||
current_chain_height: u64,
|
||||
|
@ -194,18 +264,17 @@ fn output_unlocked(
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns if a locked output, which uses a block height, can be spend.
|
||||
/// Returns if a locked output, which uses a block height, can be spent.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#block-height
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#block-height
|
||||
fn check_block_time_lock(unlock_height: u64, current_chain_height: u64) -> bool {
|
||||
// current_chain_height = 1 + top height
|
||||
unlock_height <= current_chain_height
|
||||
}
|
||||
|
||||
/// ///
|
||||
/// Returns if a locked output, which uses a block height, can be spend.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#timestamp
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#timestamp
|
||||
fn check_timestamp_time_lock(
|
||||
unlock_timestamp: u64,
|
||||
current_time_lock_timestamp: u64,
|
||||
|
@ -216,9 +285,10 @@ fn check_timestamp_time_lock(
|
|||
|
||||
/// Checks all the time locks are unlocked.
|
||||
///
|
||||
/// `current_time_lock_timestamp` must be: https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#getting-the-current-time
|
||||
/// `current_time_lock_timestamp` must be: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#getting-the-current-time
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/unlock_time.html#unlock-time
|
||||
/// https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html
|
||||
/// https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#the-output-must-not-be-locked
|
||||
fn check_all_time_locks(
|
||||
time_locks: &[Timelock],
|
||||
current_chain_height: u64,
|
||||
|
@ -243,8 +313,8 @@ fn check_all_time_locks(
|
|||
|
||||
/// Checks the decoys are allowed.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#minimum-decoys
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#equal-number-of-decoys
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#minimum-decoys
|
||||
/// && https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#equal-number-of-decoys
|
||||
fn check_decoy_info(decoy_info: &DecoyInfo, hf: &HardFork) -> Result<(), TransactionError> {
|
||||
if hf == &HardFork::V15 {
|
||||
// Hard-fork 15 allows both v14 and v16 rules
|
||||
|
@ -276,13 +346,13 @@ fn check_decoy_info(decoy_info: &DecoyInfo, hf: &HardFork) -> Result<(), Transac
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks the inputs key images for torsion and for duplicates in the transaction.
|
||||
/// Checks the inputs key images for torsion and for duplicates in the spent_kis list.
|
||||
///
|
||||
/// The `spent_kis` parameter is not meant to be a complete list of key images, just a list of related transactions
|
||||
/// key images, for example transactions in a block. The chain will be checked for duplicates later.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#unique-key-image
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#torsion-free-key-image
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#unique-key-image
|
||||
/// && https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#torsion-free-key-image
|
||||
fn check_key_images(
|
||||
input: &Input,
|
||||
spent_kis: &mut HashSet<[u8; 32]>,
|
||||
|
@ -305,7 +375,7 @@ fn check_key_images(
|
|||
|
||||
/// Checks that the input is of type [`Input::ToKey`] aka txin_to_key.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#input-type
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#input-type
|
||||
fn check_input_type(input: &Input) -> Result<(), TransactionError> {
|
||||
match input {
|
||||
Input::ToKey { .. } => Ok(()),
|
||||
|
@ -315,7 +385,7 @@ fn check_input_type(input: &Input) -> Result<(), TransactionError> {
|
|||
|
||||
/// Checks that the input has decoys.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#inputs-must-have-decoys
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#no-empty-decoys
|
||||
fn check_input_has_decoys(input: &Input) -> Result<(), TransactionError> {
|
||||
match input {
|
||||
Input::ToKey { key_offsets, .. } => {
|
||||
|
@ -331,7 +401,7 @@ fn check_input_has_decoys(input: &Input) -> Result<(), TransactionError> {
|
|||
|
||||
/// Checks that the ring members for the input are unique after hard-fork 6.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#unique-ring-members
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#unique-ring-members
|
||||
fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), TransactionError> {
|
||||
if hf >= &HardFork::V6 {
|
||||
match input {
|
||||
|
@ -351,7 +421,7 @@ fn check_ring_members_unique(input: &Input, hf: &HardFork) -> Result<(), Transac
|
|||
|
||||
/// Checks that from hf 7 the inputs are sorted by key image.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#sorted-inputs
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#sorted-inputs
|
||||
fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), TransactionError> {
|
||||
let get_ki = |inp: &Input| match inp {
|
||||
Input::ToKey { key_image, .. } => Ok(key_image.compress().to_bytes()),
|
||||
|
@ -373,7 +443,7 @@ fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), Transactio
|
|||
|
||||
/// Checks the youngest output is at least 10 blocks old.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#10-block-lock
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#10-block-lock
|
||||
fn check_10_block_lock(
|
||||
youngest_used_out_height: u64,
|
||||
current_chain_height: u64,
|
||||
|
@ -392,7 +462,7 @@ fn check_10_block_lock(
|
|||
|
||||
/// Sums the inputs checking for overflow.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#inputs-and-outputs-must-not-overflow
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#inputs-must-not-overflow
|
||||
fn sum_inputs_check_overflow(inputs: &[Input]) -> Result<u64, TransactionError> {
|
||||
let mut sum: u64 = 0;
|
||||
for inp in inputs {
|
||||
|
@ -409,7 +479,13 @@ fn sum_inputs_check_overflow(inputs: &[Input]) -> Result<u64, TransactionError>
|
|||
Ok(sum)
|
||||
}
|
||||
|
||||
/// Checks the inputs semantically validity, returning the sum of the inputs.
|
||||
///
|
||||
/// Semantic rules are rules that don't require blockchain context, the hard-fork does not require blockchain context as:
|
||||
/// - The tx-pool will use the current hard-fork
|
||||
/// - When syncing the hard-fork is in the block header.
|
||||
fn check_inputs_semantics(inputs: &[Input], hf: &HardFork) -> Result<u64, TransactionError> {
|
||||
// https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#no-empty-inputs
|
||||
if inputs.is_empty() {
|
||||
return Err(TransactionError::NoInputs);
|
||||
}
|
||||
|
@ -426,6 +502,14 @@ fn check_inputs_semantics(inputs: &[Input], hf: &HardFork) -> Result<u64, Transa
|
|||
sum_inputs_check_overflow(inputs)
|
||||
}
|
||||
|
||||
/// Checks the inputs contextual validity.
|
||||
///
|
||||
/// Contextual rules are rules that require blockchain context to check.
|
||||
///
|
||||
/// This function does not check signatures.
|
||||
///
|
||||
/// The `spent_kis` parameter is not meant to be a complete list of key images, just a list of related transactions
|
||||
/// key images, for example transactions in a block. The chain should be checked for duplicates later.
|
||||
fn check_inputs_contextual(
|
||||
inputs: &[Input],
|
||||
tx_ring_members_info: &TxRingMembersInfo,
|
||||
|
@ -454,9 +538,11 @@ fn check_inputs_contextual(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------- OVERALL
|
||||
|
||||
/// Checks the version is in the allowed range.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions.html#version
|
||||
/// https://monero-book.cuprate.org/consensus_rules/transactions.html#version
|
||||
fn check_tx_version(
|
||||
decoy_info: &Option<DecoyInfo>,
|
||||
version: &TxVersion,
|
||||
|
@ -468,7 +554,6 @@ fn check_tx_version(
|
|||
return Err(TransactionError::TransactionVersionInvalid);
|
||||
}
|
||||
|
||||
// TODO: Doc is wrong here
|
||||
let min = min_tx_version(hf);
|
||||
if version < &min && decoy_info.not_mixable == 0 {
|
||||
return Err(TransactionError::TransactionVersionInvalid);
|
||||
|
@ -483,6 +568,7 @@ fn check_tx_version(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the default maximum tx version for the given hard-fork.
|
||||
fn max_tx_version(hf: &HardFork) -> TxVersion {
|
||||
if hf <= &HardFork::V3 {
|
||||
TxVersion::RingSignatures
|
||||
|
@ -491,6 +577,7 @@ fn max_tx_version(hf: &HardFork) -> TxVersion {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the default minimum tx version for the given hard-fork.
|
||||
fn min_tx_version(hf: &HardFork) -> TxVersion {
|
||||
if hf >= &HardFork::V6 {
|
||||
TxVersion::RingCT
|
||||
|
@ -499,16 +586,42 @@ fn min_tx_version(hf: &HardFork) -> TxVersion {
|
|||
}
|
||||
}
|
||||
|
||||
fn transaction_weight_limit(hf: &HardFork) -> usize {
|
||||
penalty_free_zone(hf) / 2 - 600
|
||||
}
|
||||
|
||||
/// Checks the transaction is semantically valid.
|
||||
///
|
||||
/// Semantic rules are rules that don't require blockchain context, the hard-fork does not require blockchain context as:
|
||||
/// - The tx-pool will use the current hard-fork
|
||||
/// - When syncing the hard-fork is in the block header.
|
||||
///
|
||||
/// To fully verify a transaction this must be accompanied with [`check_transaction_contextual`]
|
||||
///
|
||||
pub fn check_transaction_semantic(
|
||||
tx: &Transaction,
|
||||
tx_blob_size: usize,
|
||||
tx_weight: usize,
|
||||
tx_hash: &[u8; 32],
|
||||
hf: &HardFork,
|
||||
verifier: &mut BatchVerifier<(), dalek_ff_group::EdwardsPoint>,
|
||||
) -> Result<u64, TransactionError> {
|
||||
// https://monero-book.cuprate.org/consensus_rules/transactions.html#transaction-size
|
||||
if tx_blob_size > MAX_TX_BLOB_SIZE
|
||||
|| (hf >= &HardFork::V8 && tx_weight > transaction_weight_limit(hf))
|
||||
{
|
||||
Err(TransactionError::TooBig)?;
|
||||
}
|
||||
|
||||
let tx_version = TxVersion::from_raw(tx.prefix.version)
|
||||
.ok_or(TransactionError::TransactionVersionInvalid)?;
|
||||
|
||||
let outputs_sum = check_outputs_semantics(&tx.prefix.outputs, hf, &tx_version)?;
|
||||
let outputs_sum = check_outputs_semantics(
|
||||
&tx.prefix.outputs,
|
||||
hf,
|
||||
&tx_version,
|
||||
&tx.rct_signatures.rct_type(),
|
||||
)?;
|
||||
let inputs_sum = check_inputs_semantics(&tx.prefix.inputs, hf)?;
|
||||
|
||||
let fee = match tx_version {
|
||||
|
@ -528,6 +641,12 @@ pub fn check_transaction_semantic(
|
|||
Ok(fee)
|
||||
}
|
||||
|
||||
/// Checks the transaction is contextually valid.
|
||||
///
|
||||
/// To fully verify a transaction this must be accompanied with [`check_transaction_semantic`]
|
||||
///
|
||||
/// `current_time_lock_timestamp` must be: https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#getting-the-current-time
|
||||
|
||||
pub fn check_transaction_contextual(
|
||||
tx: &Transaction,
|
||||
tx_ring_members_info: &TxRingMembersInfo,
|
||||
|
@ -556,7 +675,7 @@ pub fn check_transaction_contextual(
|
|||
)?;
|
||||
|
||||
match tx_version {
|
||||
TxVersion::RingSignatures => ring_signatures::verify_inputs_signatures(
|
||||
TxVersion::RingSignatures => ring_signatures::check_input_signatures(
|
||||
&tx.prefix.inputs,
|
||||
&tx.signatures,
|
||||
&tx_ring_members_info.rings,
|
||||
|
|
|
@ -135,7 +135,7 @@ impl Rings {
|
|||
#[derive(Debug)]
|
||||
pub struct TxRingMembersInfo {
|
||||
pub rings: Rings,
|
||||
/// Information on the structure of the decoys, will be [`None`] for txs before [`HardFork::V1`]
|
||||
/// Information on the structure of the decoys, must be [`None`] for txs before [`HardFork::V1`]
|
||||
pub decoy_info: Option<DecoyInfo>,
|
||||
pub youngest_used_out_height: u64,
|
||||
pub time_locked_outs: Vec<Timelock>,
|
||||
|
@ -277,7 +277,7 @@ impl DecoyInfo {
|
|||
/// Returns the default minimum amount of decoys for a hard-fork.
|
||||
/// **There are exceptions to this always being the minimum decoys**
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/decoys.html#minimum-amount-of-decoys
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#default-minimum-decoys
|
||||
pub(crate) fn minimum_decoys(hf: &HardFork) -> usize {
|
||||
use HardFork as HF;
|
||||
match hf {
|
||||
|
|
|
@ -6,7 +6,7 @@ use monero_serai::{
|
|||
mlsag::{AggregateRingMatrixBuilder, MlsagError, RingMatrix},
|
||||
RctPrunable, RctSignatures, RctType,
|
||||
},
|
||||
transaction::{Input, Output, Transaction},
|
||||
transaction::{Input, Transaction},
|
||||
H,
|
||||
};
|
||||
use multiexp::BatchVerifier;
|
||||
|
@ -27,8 +27,6 @@ const GRANDFATHERED_TRANSACTIONS: [[u8; 32]; 2] = [
|
|||
pub enum RingCTError {
|
||||
#[error("The RingCT type used is not allowed.")]
|
||||
TypeNotAllowed,
|
||||
#[error("One or more of the outputs do not have a zero amount.")]
|
||||
OutputNotZero,
|
||||
#[error("RingCT simple: sum pseudo-outs does not equal outputs.")]
|
||||
SimpleAmountDoNotBalance,
|
||||
#[error("The borromean range proof is invalid.")]
|
||||
|
@ -61,19 +59,6 @@ fn check_rct_type(ty: &RctType, hf: HardFork, tx_hash: &[u8; 32]) -> Result<(),
|
|||
}
|
||||
}
|
||||
|
||||
/// Checks all the outputs have a zero amount.
|
||||
///
|
||||
/// https://monero-book.cuprate.org/consensus_rules/ring_ct.html#output-amount
|
||||
fn check_output_amount(outputs: &[Output]) -> Result<(), RingCTError> {
|
||||
outputs.iter().try_for_each(|out| {
|
||||
if out.amount.is_none() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RingCTError::OutputNotZero)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks that the pseudo-outs sum to the same point as the output commitments.
|
||||
///
|
||||
/// https://monero-book.cuprate.org/consensus_rules/ring_ct.html#pseudo-outs-outpks-balance
|
||||
|
@ -133,28 +118,29 @@ fn check_output_range_proofs(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn ring_ct_semantic_checks(
|
||||
pub(crate) fn ring_ct_semantic_checks(
|
||||
tx: &Transaction,
|
||||
tx_hash: &[u8; 32],
|
||||
verifier: &mut BatchVerifier<(), dalek_ff_group::EdwardsPoint>,
|
||||
hf: &HardFork,
|
||||
) -> Result<(), RingCTError> {
|
||||
check_output_amount(&tx.prefix.outputs)?;
|
||||
check_rct_type(&tx.rct_signatures.rct_type(), *hf, tx_hash)?;
|
||||
let rct_type = tx.rct_signatures.rct_type();
|
||||
|
||||
check_rct_type(&rct_type, *hf, tx_hash)?;
|
||||
check_output_range_proofs(&tx.rct_signatures, verifier)?;
|
||||
|
||||
if tx.rct_signatures.rct_type() != RctType::MlsagAggregate {
|
||||
if rct_type != RctType::MlsagAggregate {
|
||||
simple_type_balances(&tx.rct_signatures)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check the input signatures, MLSAG, CLSAG.
|
||||
/// Check the input signatures: MLSAG, CLSAG.
|
||||
///
|
||||
/// https://monero-book.cuprate.org/consensus_rules/ring_ct/mlsag.html
|
||||
/// https://monero-book.cuprate.org/consensus_rules/ring_ct/clsag.html
|
||||
pub fn check_input_signatures(
|
||||
pub(crate) fn check_input_signatures(
|
||||
msg: &[u8; 32],
|
||||
inputs: &[Input],
|
||||
rct_sig: &RctSignatures,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
//! Version 1 ring signature verification.
|
||||
//!
|
||||
//! Some checks have to be done at deserialization or with data we don't have so we can't do them here, those checks are:
|
||||
//! https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#signatures-must-be-canonical
|
||||
//! https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html#signatures-must-be-canonical
|
||||
//! this happens at deserialization in monero-serai.
|
||||
//! https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#amount-of-signatures-in-a-ring
|
||||
//! https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html#amount-of-signatures-in-a-ring
|
||||
//! and this happens during ring signature verification in monero-serai.
|
||||
//!
|
||||
use monero_serai::{ring_signatures::RingSignature, transaction::Input};
|
||||
|
@ -16,9 +16,8 @@ use crate::try_par_iter;
|
|||
|
||||
/// Verifies the ring signature.
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#the-ring-signature-must-be-valid
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/transactions/pre_rct.html#amount-of-ring-signatures
|
||||
pub fn verify_inputs_signatures(
|
||||
/// ref: https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html
|
||||
pub fn check_input_signatures(
|
||||
inputs: &[Input],
|
||||
signatures: &[RingSignature],
|
||||
rings: &Rings,
|
||||
|
@ -26,6 +25,7 @@ pub fn verify_inputs_signatures(
|
|||
) -> Result<(), TransactionError> {
|
||||
match rings {
|
||||
Rings::Legacy(rings) => {
|
||||
// https://monero-book.cuprate.org/consensus_rules/transactions/ring_signatures.html#amount-of-ring-signatures
|
||||
// rings.len() != inputs.len() can't happen but check any way.
|
||||
if signatures.len() != inputs.len() || rings.len() != inputs.len() {
|
||||
return Err(TransactionError::RingSignatureIncorrect);
|
||||
|
|
|
@ -16,6 +16,8 @@ use rayon::prelude::*;
|
|||
use tower::ServiceExt;
|
||||
use tracing::instrument;
|
||||
|
||||
use monero_consensus::blocks::{penalty_free_zone, PENALTY_FREE_ZONE_5};
|
||||
|
||||
use crate::{
|
||||
helper::{median, rayon_spawn_async},
|
||||
Database, DatabaseRequest, DatabaseResponse, ExtendedConsensusError, HardFork,
|
||||
|
@ -24,26 +26,9 @@ use crate::{
|
|||
#[cfg(test)]
|
||||
pub(super) mod tests;
|
||||
|
||||
const PENALTY_FREE_ZONE_1: usize = 20000;
|
||||
const PENALTY_FREE_ZONE_2: usize = 60000;
|
||||
const PENALTY_FREE_ZONE_5: usize = 300000;
|
||||
|
||||
const SHORT_TERM_WINDOW: u64 = 100;
|
||||
const LONG_TERM_WINDOW: u64 = 100000;
|
||||
|
||||
/// Returns the penalty free zone
|
||||
///
|
||||
/// https://cuprate.github.io/monero-book/consensus_rules/blocks/weight_limit.html#penalty-free-zone
|
||||
pub fn penalty_free_zone(hf: &HardFork) -> usize {
|
||||
if hf == &HardFork::V1 {
|
||||
PENALTY_FREE_ZONE_1
|
||||
} else if hf >= &HardFork::V2 && hf < &HardFork::V5 {
|
||||
PENALTY_FREE_ZONE_2
|
||||
} else {
|
||||
PENALTY_FREE_ZONE_5
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for the block weight cache.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -8,6 +8,7 @@ use std::{
|
|||
};
|
||||
|
||||
use futures::FutureExt;
|
||||
use monero_serai::ringct::RctType;
|
||||
use monero_serai::transaction::Transaction;
|
||||
use rayon::prelude::*;
|
||||
use tower::{Service, ServiceExt};
|
||||
|
@ -50,16 +51,26 @@ impl TransactionVerificationData {
|
|||
verifier: Arc<MultiThreadedBatchVerifier>,
|
||||
) -> Result<TransactionVerificationData, ConsensusError> {
|
||||
let tx_hash = tx.hash();
|
||||
let tx_blob = tx.serialize();
|
||||
|
||||
// the tx weight is only different from the blobs length for bp(+) txs.
|
||||
let tx_weight = match tx.rct_signatures.rct_type() {
|
||||
RctType::Bulletproofs
|
||||
| RctType::BulletproofsCompactAmount
|
||||
| RctType::Clsag
|
||||
| RctType::BulletproofsPlus => tx.weight(),
|
||||
_ => tx_blob.len(),
|
||||
};
|
||||
|
||||
let fee = verifier.queue_statement(|verifier| {
|
||||
check_transaction_semantic(&tx, &tx_hash, hf, verifier)
|
||||
check_transaction_semantic(&tx, tx_blob.len(), tx_weight, &tx_hash, hf, verifier)
|
||||
.map_err(ConsensusError::Transaction)
|
||||
})?;
|
||||
|
||||
Ok(TransactionVerificationData {
|
||||
tx_hash,
|
||||
tx_blob: tx.serialize(),
|
||||
tx_weight: tx.weight(),
|
||||
tx_blob,
|
||||
tx_weight,
|
||||
fee,
|
||||
rings_member_info: std::sync::Mutex::new(None),
|
||||
version: TxVersion::from_raw(tx.prefix.version)
|
||||
|
|
Loading…
Reference in a new issue