mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
Enable NRD kernel support (noop) feature flagged (#3303)
* wip * commit * add block level validation rule around NRD kernels and HF3 block height * wip * test coverage for kernel ser/deser for NRD kernels * add some type safety around ser/deser of nrd relative height * cleanup * cleanup tx sig handling and add tests around NRD kernel sig * test coverage for chain apply block containing NRD kernel * verify kernel variants when adding tx to pool NRD kernels will not be accepted until HF3 * add txpool test for NRD kernel handling * fix merge * fix api for NRD kernels * commit * wip - feature flag for NRD kernel support * wip * global config and thread local feature flag for NRD kernel support * add feature flag support for NRD kernels default to false when starting up node allow it to be set to true in context of individual tests * feature flag * explicit testing of NRD kernel via feature flag * add chain_type and feature flag logging on startup * PR feedback and cleanup * PR feedback
This commit is contained in:
parent
731528c616
commit
988a05f023
16 changed files with 1097 additions and 77 deletions
|
@ -509,6 +509,10 @@ impl TxKernelPrintable {
|
||||||
KernelFeatures::Plain { fee } => (fee, 0),
|
KernelFeatures::Plain { fee } => (fee, 0),
|
||||||
KernelFeatures::Coinbase => (0, 0),
|
KernelFeatures::Coinbase => (0, 0),
|
||||||
KernelFeatures::HeightLocked { fee, lock_height } => (fee, lock_height),
|
KernelFeatures::HeightLocked { fee, lock_height } => (fee, lock_height),
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee,
|
||||||
|
relative_height,
|
||||||
|
} => (fee, relative_height.into()),
|
||||||
};
|
};
|
||||||
TxKernelPrintable {
|
TxKernelPrintable {
|
||||||
features,
|
features,
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub fn init_chain(dir_name: &str, genesis: Block) -> Chain {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build genesis block with reward (non-empty, like we have in mainnet).
|
/// Build genesis block with reward (non-empty, like we have in mainnet).
|
||||||
fn genesis_block<K>(keychain: &K) -> Block
|
pub fn genesis_block<K>(keychain: &K) -> Block
|
||||||
where
|
where
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
{
|
{
|
||||||
|
@ -69,6 +69,7 @@ where
|
||||||
|
|
||||||
/// Mine a chain of specified length to assist with automated tests.
|
/// Mine a chain of specified length to assist with automated tests.
|
||||||
/// Probably a good idea to call clean_output_dir at the beginning and end of each test.
|
/// Probably a good idea to call clean_output_dir at the beginning and end of each test.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn mine_chain(dir_name: &str, chain_length: u64) -> Chain {
|
pub fn mine_chain(dir_name: &str, chain_length: u64) -> Chain {
|
||||||
global::set_local_chain_type(ChainTypes::AutomatedTesting);
|
global::set_local_chain_type(ChainTypes::AutomatedTesting);
|
||||||
let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap();
|
let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap();
|
||||||
|
@ -78,6 +79,7 @@ pub fn mine_chain(dir_name: &str, chain_length: u64) -> Chain {
|
||||||
chain
|
chain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
fn mine_some_on_top<K>(chain: &mut Chain, chain_length: u64, keychain: &K)
|
fn mine_some_on_top<K>(chain: &mut Chain, chain_length: u64, keychain: &K)
|
||||||
where
|
where
|
||||||
K: Keychain,
|
K: Keychain,
|
||||||
|
|
151
chain/tests/mine_nrd_kernel.rs
Normal file
151
chain/tests/mine_nrd_kernel.rs
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
// Copyright 2020 The Grin Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
mod chain_test_helper;
|
||||||
|
|
||||||
|
use grin_chain as chain;
|
||||||
|
use grin_core as core;
|
||||||
|
use grin_keychain as keychain;
|
||||||
|
use grin_util as util;
|
||||||
|
|
||||||
|
use self::chain_test_helper::{clean_output_dir, genesis_block, init_chain};
|
||||||
|
use crate::chain::{Chain, Options};
|
||||||
|
use crate::core::core::{Block, KernelFeatures, NRDRelativeHeight, Transaction};
|
||||||
|
use crate::core::libtx::{build, reward, ProofBuilder};
|
||||||
|
use crate::core::{consensus, global, pow};
|
||||||
|
use crate::keychain::{ExtKeychain, ExtKeychainPath, Identifier, Keychain};
|
||||||
|
use chrono::Duration;
|
||||||
|
|
||||||
|
fn build_block<K>(chain: &Chain, keychain: &K, key_id: &Identifier, txs: Vec<Transaction>) -> Block
|
||||||
|
where
|
||||||
|
K: Keychain,
|
||||||
|
{
|
||||||
|
let prev = chain.head_header().unwrap();
|
||||||
|
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
||||||
|
let fee = txs.iter().map(|x| x.fee()).sum();
|
||||||
|
let reward =
|
||||||
|
reward::output(keychain, &ProofBuilder::new(keychain), key_id, fee, false).unwrap();
|
||||||
|
|
||||||
|
let mut block = Block::new(&prev, txs, next_header_info.clone().difficulty, reward).unwrap();
|
||||||
|
|
||||||
|
block.header.timestamp = prev.timestamp + Duration::seconds(60);
|
||||||
|
block.header.pow.secondary_scaling = next_header_info.secondary_scaling;
|
||||||
|
|
||||||
|
chain.set_txhashset_roots(&mut block).unwrap();
|
||||||
|
|
||||||
|
let edge_bits = global::min_edge_bits();
|
||||||
|
block.header.pow.proof.edge_bits = edge_bits;
|
||||||
|
pow::pow_size(
|
||||||
|
&mut block.header,
|
||||||
|
next_header_info.difficulty,
|
||||||
|
global::proofsize(),
|
||||||
|
edge_bits,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
block
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mine_block_with_nrd_kernel_and_nrd_feature_enabled() {
|
||||||
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_nrd_enabled(true);
|
||||||
|
|
||||||
|
util::init_test_logger();
|
||||||
|
|
||||||
|
let chain_dir = ".grin.nrd_kernel";
|
||||||
|
clean_output_dir(chain_dir);
|
||||||
|
|
||||||
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||||
|
let pb = ProofBuilder::new(&keychain);
|
||||||
|
let genesis = genesis_block(&keychain);
|
||||||
|
let chain = init_chain(chain_dir, genesis.clone());
|
||||||
|
|
||||||
|
for n in 1..9 {
|
||||||
|
let key_id = ExtKeychainPath::new(1, n, 0, 0, 0).to_identifier();
|
||||||
|
let block = build_block(&chain, &keychain, &key_id, vec![]);
|
||||||
|
chain.process_block(block, Options::MINE).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(chain.head().unwrap().height, 8);
|
||||||
|
|
||||||
|
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
||||||
|
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
|
||||||
|
let tx = build::transaction(
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 20000,
|
||||||
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
|
},
|
||||||
|
vec![
|
||||||
|
build::coinbase_input(consensus::REWARD, key_id1.clone()),
|
||||||
|
build::output(consensus::REWARD - 20000, key_id2.clone()),
|
||||||
|
],
|
||||||
|
&keychain,
|
||||||
|
&pb,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let key_id9 = ExtKeychainPath::new(1, 9, 0, 0, 0).to_identifier();
|
||||||
|
let block = build_block(&chain, &keychain, &key_id9, vec![tx]);
|
||||||
|
chain.process_block(block, Options::MINE).unwrap();
|
||||||
|
chain.validate(false).unwrap();
|
||||||
|
|
||||||
|
clean_output_dir(chain_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mine_invalid_block_with_nrd_kernel_and_nrd_feature_enabled_before_hf() {
|
||||||
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_nrd_enabled(true);
|
||||||
|
|
||||||
|
util::init_test_logger();
|
||||||
|
|
||||||
|
let chain_dir = ".grin.invalid_nrd_kernel";
|
||||||
|
clean_output_dir(chain_dir);
|
||||||
|
|
||||||
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||||
|
let pb = ProofBuilder::new(&keychain);
|
||||||
|
let genesis = genesis_block(&keychain);
|
||||||
|
let chain = init_chain(chain_dir, genesis.clone());
|
||||||
|
|
||||||
|
for n in 1..8 {
|
||||||
|
let key_id = ExtKeychainPath::new(1, n, 0, 0, 0).to_identifier();
|
||||||
|
let block = build_block(&chain, &keychain, &key_id, vec![]);
|
||||||
|
chain.process_block(block, Options::MINE).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(chain.head().unwrap().height, 7);
|
||||||
|
|
||||||
|
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
||||||
|
let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier();
|
||||||
|
let tx = build::transaction(
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 20000,
|
||||||
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
|
},
|
||||||
|
vec![
|
||||||
|
build::coinbase_input(consensus::REWARD, key_id1.clone()),
|
||||||
|
build::output(consensus::REWARD - 20000, key_id2.clone()),
|
||||||
|
],
|
||||||
|
&keychain,
|
||||||
|
&pb,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let key_id8 = ExtKeychainPath::new(1, 8, 0, 0, 0).to_identifier();
|
||||||
|
let block = build_block(&chain, &keychain, &key_id8, vec![tx]);
|
||||||
|
let res = chain.process_block(block, Options::MINE);
|
||||||
|
assert!(res.is_err());
|
||||||
|
clean_output_dir(chain_dir);
|
||||||
|
}
|
|
@ -81,7 +81,7 @@ fn mine_empty_chain() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mine_short_chain() {
|
fn mine_short_chain() {
|
||||||
let chain_dir = ".grin.genesis";
|
let chain_dir = ".grin.short";
|
||||||
clean_output_dir(chain_dir);
|
clean_output_dir(chain_dir);
|
||||||
let chain = mine_chain(chain_dir, 4);
|
let chain = mine_chain(chain_dir, 4);
|
||||||
assert_eq!(chain.head().unwrap().height, 3);
|
assert_eq!(chain.head().unwrap().height, 3);
|
||||||
|
|
|
@ -133,12 +133,15 @@ pub const FLOONET_FIRST_HARD_FORK: u64 = 185_040;
|
||||||
/// Floonet second hard fork height, set to happen around 2019-12-19
|
/// Floonet second hard fork height, set to happen around 2019-12-19
|
||||||
pub const FLOONET_SECOND_HARD_FORK: u64 = 298_080;
|
pub const FLOONET_SECOND_HARD_FORK: u64 = 298_080;
|
||||||
|
|
||||||
/// AutomatedTesting and UserTesting first hard fork height.
|
/// AutomatedTesting and UserTesting HF1 height.
|
||||||
pub const TESTING_FIRST_HARD_FORK: u64 = 3;
|
pub const TESTING_FIRST_HARD_FORK: u64 = 3;
|
||||||
|
|
||||||
/// AutomatedTesting and UserTesting second hard fork height.
|
/// AutomatedTesting and UserTesting HF2 height.
|
||||||
pub const TESTING_SECOND_HARD_FORK: u64 = 6;
|
pub const TESTING_SECOND_HARD_FORK: u64 = 6;
|
||||||
|
|
||||||
|
/// AutomatedTesting and UserTesting HF3 height.
|
||||||
|
pub const TESTING_THIRD_HARD_FORK: u64 = 9;
|
||||||
|
|
||||||
/// Compute possible block version at a given height, implements
|
/// Compute possible block version at a given height, implements
|
||||||
/// 6 months interval scheduled hard forks for the first 2 years.
|
/// 6 months interval scheduled hard forks for the first 2 years.
|
||||||
pub fn header_version(height: u64) -> HeaderVersion {
|
pub fn header_version(height: u64) -> HeaderVersion {
|
||||||
|
@ -162,10 +165,12 @@ pub fn header_version(height: u64) -> HeaderVersion {
|
||||||
HeaderVersion(1)
|
HeaderVersion(1)
|
||||||
} else if height < TESTING_SECOND_HARD_FORK {
|
} else if height < TESTING_SECOND_HARD_FORK {
|
||||||
HeaderVersion(2)
|
HeaderVersion(2)
|
||||||
} else if height < 3 * HARD_FORK_INTERVAL {
|
} else if height < TESTING_THIRD_HARD_FORK {
|
||||||
HeaderVersion(3)
|
HeaderVersion(3)
|
||||||
|
} else if height < 4 * HARD_FORK_INTERVAL {
|
||||||
|
HeaderVersion(4)
|
||||||
} else {
|
} else {
|
||||||
HeaderVersion(hf_interval)
|
HeaderVersion(5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,10 @@ pub enum Error {
|
||||||
InvalidPow,
|
InvalidPow,
|
||||||
/// Kernel not valid due to lock_height exceeding block header height
|
/// Kernel not valid due to lock_height exceeding block header height
|
||||||
KernelLockHeight(u64),
|
KernelLockHeight(u64),
|
||||||
|
/// NRD kernels are not valid prior to HF3.
|
||||||
|
NRDKernelPreHF3,
|
||||||
|
/// NRD kernels are not valid if disabled locally via "feature flag".
|
||||||
|
NRDKernelNotEnabled,
|
||||||
/// Underlying tx related error
|
/// Underlying tx related error
|
||||||
Transaction(transaction::Error),
|
Transaction(transaction::Error),
|
||||||
/// Underlying Secp256k1 error (signature validation or invalid public key
|
/// Underlying Secp256k1 error (signature validation or invalid public key
|
||||||
|
@ -753,6 +757,7 @@ impl Block {
|
||||||
self.body.validate(Weighting::AsBlock, verifier)?;
|
self.body.validate(Weighting::AsBlock, verifier)?;
|
||||||
|
|
||||||
self.verify_kernel_lock_heights()?;
|
self.verify_kernel_lock_heights()?;
|
||||||
|
self.verify_nrd_kernels_for_header_version()?;
|
||||||
self.verify_coinbase()?;
|
self.verify_coinbase()?;
|
||||||
|
|
||||||
// take the kernel offset for this block (block offset minus previous) and
|
// take the kernel offset for this block (block offset minus previous) and
|
||||||
|
@ -802,6 +807,7 @@ impl Block {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify any absolute kernel lock heights.
|
||||||
fn verify_kernel_lock_heights(&self) -> Result<(), Error> {
|
fn verify_kernel_lock_heights(&self) -> Result<(), Error> {
|
||||||
for k in &self.body.kernels {
|
for k in &self.body.kernels {
|
||||||
// check we have no kernels with lock_heights greater than current height
|
// check we have no kernels with lock_heights greater than current height
|
||||||
|
@ -814,6 +820,21 @@ impl Block {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NRD kernels are not valid if the global feature flag is disabled.
|
||||||
|
// NRD kernels were introduced in HF3 and are not valid for block version < 4.
|
||||||
|
// Blocks prior to HF3 containing any NRD kernel(s) are invalid.
|
||||||
|
fn verify_nrd_kernels_for_header_version(&self) -> Result<(), Error> {
|
||||||
|
if self.body.kernels.iter().any(|k| k.is_nrd()) {
|
||||||
|
if !global::is_nrd_enabled() {
|
||||||
|
return Err(Error::NRDKernelNotEnabled);
|
||||||
|
}
|
||||||
|
if self.header.version < HeaderVersion(4) {
|
||||||
|
return Err(Error::NRDKernelPreHF3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<UntrustedBlock> for Block {
|
impl From<UntrustedBlock> for Block {
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
use crate::core::hash::{DefaultHashable, Hashed};
|
use crate::core::hash::{DefaultHashable, Hashed};
|
||||||
use crate::core::verifier_cache::VerifierCache;
|
use crate::core::verifier_cache::VerifierCache;
|
||||||
use crate::core::{committed, Committed};
|
use crate::core::{committed, Committed};
|
||||||
use crate::libtx::secp_ser;
|
use crate::libtx::{aggsig, secp_ser};
|
||||||
use crate::ser::{
|
use crate::ser::{
|
||||||
self, read_multi, PMMRable, ProtocolVersion, Readable, Reader, VerifySortedAndUnique,
|
self, read_multi, PMMRable, ProtocolVersion, Readable, Reader, VerifySortedAndUnique,
|
||||||
Writeable, Writer,
|
Writeable, Writer,
|
||||||
|
@ -27,7 +27,7 @@ use enum_primitive::FromPrimitive;
|
||||||
use keychain::{self, BlindingFactor};
|
use keychain::{self, BlindingFactor};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::convert::TryInto;
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{error, fmt};
|
use std::{error, fmt};
|
||||||
use util::secp;
|
use util::secp;
|
||||||
|
@ -36,6 +36,70 @@ use util::static_secp_instance;
|
||||||
use util::RwLock;
|
use util::RwLock;
|
||||||
use util::ToHex;
|
use util::ToHex;
|
||||||
|
|
||||||
|
/// Relative height field on NRD kernel variant.
|
||||||
|
/// u16 representing a height between 1 and MAX (consensus::WEEK_HEIGHT).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct NRDRelativeHeight(u16);
|
||||||
|
|
||||||
|
impl DefaultHashable for NRDRelativeHeight {}
|
||||||
|
|
||||||
|
impl Writeable for NRDRelativeHeight {
|
||||||
|
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||||
|
writer.write_u16(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Readable for NRDRelativeHeight {
|
||||||
|
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> {
|
||||||
|
let x = reader.read_u16()?;
|
||||||
|
NRDRelativeHeight::try_from(x).map_err(|_| ser::Error::CorruptedData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conversion from a u16 to a valid NRDRelativeHeight.
|
||||||
|
/// Valid height is between 1 and WEEK_HEIGHT inclusive.
|
||||||
|
impl TryFrom<u16> for NRDRelativeHeight {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(height: u16) -> Result<Self, Self::Error> {
|
||||||
|
if height == 0 {
|
||||||
|
Err(Error::InvalidNRDRelativeHeight)
|
||||||
|
} else if height
|
||||||
|
> NRDRelativeHeight::MAX
|
||||||
|
.try_into()
|
||||||
|
.expect("WEEK_HEIGHT const should fit in u16")
|
||||||
|
{
|
||||||
|
Err(Error::InvalidNRDRelativeHeight)
|
||||||
|
} else {
|
||||||
|
Ok(Self(height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u64> for NRDRelativeHeight {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(height: u64) -> Result<Self, Self::Error> {
|
||||||
|
Self::try_from(u16::try_from(height).map_err(|_| Error::InvalidNRDRelativeHeight)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NRDRelativeHeight> for u64 {
|
||||||
|
fn from(height: NRDRelativeHeight) -> Self {
|
||||||
|
height.0 as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NRDRelativeHeight {
|
||||||
|
const MAX: u64 = consensus::WEEK_HEIGHT;
|
||||||
|
|
||||||
|
/// Create a new NRDRelativeHeight from the provided height.
|
||||||
|
/// Checks height is valid (between 1 and WEEK_HEIGHT inclusive).
|
||||||
|
pub fn new(height: u64) -> Result<Self, Error> {
|
||||||
|
NRDRelativeHeight::try_from(height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Various tx kernel variants.
|
/// Various tx kernel variants.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum KernelFeatures {
|
pub enum KernelFeatures {
|
||||||
|
@ -53,12 +117,20 @@ pub enum KernelFeatures {
|
||||||
/// Height locked kernels have lock heights.
|
/// Height locked kernels have lock heights.
|
||||||
lock_height: u64,
|
lock_height: u64,
|
||||||
},
|
},
|
||||||
|
/// "No Recent Duplicate" (NRD) kernels enforcing relative lock height between instances.
|
||||||
|
NoRecentDuplicate {
|
||||||
|
/// These have fees.
|
||||||
|
fee: u64,
|
||||||
|
/// Relative lock height.
|
||||||
|
relative_height: NRDRelativeHeight,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KernelFeatures {
|
impl KernelFeatures {
|
||||||
const PLAIN_U8: u8 = 0;
|
const PLAIN_U8: u8 = 0;
|
||||||
const COINBASE_U8: u8 = 1;
|
const COINBASE_U8: u8 = 1;
|
||||||
const HEIGHT_LOCKED_U8: u8 = 2;
|
const HEIGHT_LOCKED_U8: u8 = 2;
|
||||||
|
const NO_RECENT_DUPLICATE_U8: u8 = 3;
|
||||||
|
|
||||||
/// Underlying (u8) value representing this kernel variant.
|
/// Underlying (u8) value representing this kernel variant.
|
||||||
/// This is the first byte when we serialize/deserialize the kernel features.
|
/// This is the first byte when we serialize/deserialize the kernel features.
|
||||||
|
@ -67,6 +139,7 @@ impl KernelFeatures {
|
||||||
KernelFeatures::Plain { .. } => KernelFeatures::PLAIN_U8,
|
KernelFeatures::Plain { .. } => KernelFeatures::PLAIN_U8,
|
||||||
KernelFeatures::Coinbase => KernelFeatures::COINBASE_U8,
|
KernelFeatures::Coinbase => KernelFeatures::COINBASE_U8,
|
||||||
KernelFeatures::HeightLocked { .. } => KernelFeatures::HEIGHT_LOCKED_U8,
|
KernelFeatures::HeightLocked { .. } => KernelFeatures::HEIGHT_LOCKED_U8,
|
||||||
|
KernelFeatures::NoRecentDuplicate { .. } => KernelFeatures::NO_RECENT_DUPLICATE_U8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,18 +149,24 @@ impl KernelFeatures {
|
||||||
KernelFeatures::Plain { .. } => String::from("Plain"),
|
KernelFeatures::Plain { .. } => String::from("Plain"),
|
||||||
KernelFeatures::Coinbase => String::from("Coinbase"),
|
KernelFeatures::Coinbase => String::from("Coinbase"),
|
||||||
KernelFeatures::HeightLocked { .. } => String::from("HeightLocked"),
|
KernelFeatures::HeightLocked { .. } => String::from("HeightLocked"),
|
||||||
|
KernelFeatures::NoRecentDuplicate { .. } => String::from("NoRecentDuplicate"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// msg = hash(features) for coinbase kernels
|
/// msg = hash(features) for coinbase kernels
|
||||||
/// hash(features || fee) for plain kernels
|
/// hash(features || fee) for plain kernels
|
||||||
/// hash(features || fee || lock_height) for height locked kernels
|
/// hash(features || fee || lock_height) for height locked kernels
|
||||||
|
/// hash(features || fee || relative_height) for NRD kernels
|
||||||
pub fn kernel_sig_msg(&self) -> Result<secp::Message, Error> {
|
pub fn kernel_sig_msg(&self) -> Result<secp::Message, Error> {
|
||||||
let x = self.as_u8();
|
let x = self.as_u8();
|
||||||
let hash = match self {
|
let hash = match self {
|
||||||
KernelFeatures::Plain { fee } => (x, fee).hash(),
|
KernelFeatures::Plain { fee } => (x, fee).hash(),
|
||||||
KernelFeatures::Coinbase => (x).hash(),
|
KernelFeatures::Coinbase => x.hash(),
|
||||||
KernelFeatures::HeightLocked { fee, lock_height } => (x, fee, lock_height).hash(),
|
KernelFeatures::HeightLocked { fee, lock_height } => (x, fee, lock_height).hash(),
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee,
|
||||||
|
relative_height,
|
||||||
|
} => (x, fee, relative_height).hash(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let msg = secp::Message::from_slice(&hash.as_bytes())?;
|
let msg = secp::Message::from_slice(&hash.as_bytes())?;
|
||||||
|
@ -97,14 +176,36 @@ impl KernelFeatures {
|
||||||
/// Write tx kernel features out in v1 protocol format.
|
/// Write tx kernel features out in v1 protocol format.
|
||||||
/// Always include the fee and lock_height, writing 0 value if unused.
|
/// Always include the fee and lock_height, writing 0 value if unused.
|
||||||
fn write_v1<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
fn write_v1<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||||
let (fee, lock_height) = match self {
|
|
||||||
KernelFeatures::Plain { fee } => (*fee, 0),
|
|
||||||
KernelFeatures::Coinbase => (0, 0),
|
|
||||||
KernelFeatures::HeightLocked { fee, lock_height } => (*fee, *lock_height),
|
|
||||||
};
|
|
||||||
writer.write_u8(self.as_u8())?;
|
writer.write_u8(self.as_u8())?;
|
||||||
writer.write_u64(fee)?;
|
match self {
|
||||||
writer.write_u64(lock_height)?;
|
KernelFeatures::Plain { fee } => {
|
||||||
|
writer.write_u64(*fee)?;
|
||||||
|
// Write "empty" bytes for feature specific data (8 bytes).
|
||||||
|
writer.write_empty_bytes(8)?;
|
||||||
|
}
|
||||||
|
KernelFeatures::Coinbase => {
|
||||||
|
// Write "empty" bytes for fee (8 bytes) and feature specific data (8 bytes).
|
||||||
|
writer.write_empty_bytes(16)?;
|
||||||
|
}
|
||||||
|
KernelFeatures::HeightLocked { fee, lock_height } => {
|
||||||
|
writer.write_u64(*fee)?;
|
||||||
|
// 8 bytes of feature specific data containing the lock height as big-endian u64.
|
||||||
|
writer.write_u64(*lock_height)?;
|
||||||
|
}
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee,
|
||||||
|
relative_height,
|
||||||
|
} => {
|
||||||
|
writer.write_u64(*fee)?;
|
||||||
|
|
||||||
|
// 8 bytes of feature specific data. First 6 bytes are empty.
|
||||||
|
// Last 2 bytes contain the relative lock height as big-endian u16.
|
||||||
|
// Note: This is effectively the same as big-endian u64.
|
||||||
|
// We write "empty" bytes explicitly rather than quietly casting the u16 -> u64.
|
||||||
|
writer.write_empty_bytes(6)?;
|
||||||
|
relative_height.write(writer)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,48 +214,75 @@ impl KernelFeatures {
|
||||||
/// Only write fee out for feature variants that support it.
|
/// Only write fee out for feature variants that support it.
|
||||||
/// Only write lock_height out for feature variants that support it.
|
/// Only write lock_height out for feature variants that support it.
|
||||||
fn write_v2<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
fn write_v2<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||||
|
writer.write_u8(self.as_u8())?;
|
||||||
match self {
|
match self {
|
||||||
KernelFeatures::Plain { fee } => {
|
KernelFeatures::Plain { fee } => {
|
||||||
writer.write_u8(self.as_u8())?;
|
// Fee only, no additional data on plain kernels.
|
||||||
writer.write_u64(*fee)?;
|
writer.write_u64(*fee)?;
|
||||||
}
|
}
|
||||||
KernelFeatures::Coinbase => {
|
KernelFeatures::Coinbase => {
|
||||||
writer.write_u8(self.as_u8())?;
|
// No additional data.
|
||||||
}
|
}
|
||||||
KernelFeatures::HeightLocked { fee, lock_height } => {
|
KernelFeatures::HeightLocked { fee, lock_height } => {
|
||||||
writer.write_u8(self.as_u8())?;
|
|
||||||
writer.write_u64(*fee)?;
|
writer.write_u64(*fee)?;
|
||||||
|
// V2 height locked kernels use 8 bytes for the lock height.
|
||||||
writer.write_u64(*lock_height)?;
|
writer.write_u64(*lock_height)?;
|
||||||
}
|
}
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee,
|
||||||
|
relative_height,
|
||||||
|
} => {
|
||||||
|
writer.write_u64(*fee)?;
|
||||||
|
// V2 NRD kernels use 2 bytes for the relative lock height.
|
||||||
|
relative_height.write(writer)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always read feature byte, 8 bytes for fee and 8 bytes for lock height.
|
// Always read feature byte, 8 bytes for fee and 8 bytes for additional data
|
||||||
// Fee and lock height may be unused for some kernel variants but we need
|
// representing lock height or relative height.
|
||||||
|
// Fee and additional data may be unused for some kernel variants but we need
|
||||||
// to read these bytes and verify they are 0 if unused.
|
// to read these bytes and verify they are 0 if unused.
|
||||||
fn read_v1<R: Reader>(reader: &mut R) -> Result<KernelFeatures, ser::Error> {
|
fn read_v1<R: Reader>(reader: &mut R) -> Result<KernelFeatures, ser::Error> {
|
||||||
let feature_byte = reader.read_u8()?;
|
let feature_byte = reader.read_u8()?;
|
||||||
let fee = reader.read_u64()?;
|
|
||||||
let lock_height = reader.read_u64()?;
|
|
||||||
|
|
||||||
let features = match feature_byte {
|
let features = match feature_byte {
|
||||||
KernelFeatures::PLAIN_U8 => {
|
KernelFeatures::PLAIN_U8 => {
|
||||||
if lock_height != 0 {
|
let fee = reader.read_u64()?;
|
||||||
return Err(ser::Error::CorruptedData);
|
// 8 "empty" bytes as additional data is not used.
|
||||||
}
|
reader.read_empty_bytes(8)?;
|
||||||
KernelFeatures::Plain { fee }
|
KernelFeatures::Plain { fee }
|
||||||
}
|
}
|
||||||
KernelFeatures::COINBASE_U8 => {
|
KernelFeatures::COINBASE_U8 => {
|
||||||
if fee != 0 {
|
// 8 "empty" bytes as fee is not used.
|
||||||
return Err(ser::Error::CorruptedData);
|
// 8 "empty" bytes as additional data is not used.
|
||||||
}
|
reader.read_empty_bytes(16)?;
|
||||||
if lock_height != 0 {
|
|
||||||
return Err(ser::Error::CorruptedData);
|
|
||||||
}
|
|
||||||
KernelFeatures::Coinbase
|
KernelFeatures::Coinbase
|
||||||
}
|
}
|
||||||
KernelFeatures::HEIGHT_LOCKED_U8 => KernelFeatures::HeightLocked { fee, lock_height },
|
KernelFeatures::HEIGHT_LOCKED_U8 => {
|
||||||
|
let fee = reader.read_u64()?;
|
||||||
|
// 8 bytes of feature specific data, lock height as big-endian u64.
|
||||||
|
let lock_height = reader.read_u64()?;
|
||||||
|
KernelFeatures::HeightLocked { fee, lock_height }
|
||||||
|
}
|
||||||
|
KernelFeatures::NO_RECENT_DUPLICATE_U8 => {
|
||||||
|
// NRD kernels are invalid if NRD feature flag is not enabled.
|
||||||
|
if !global::is_nrd_enabled() {
|
||||||
|
return Err(ser::Error::CorruptedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fee = reader.read_u64()?;
|
||||||
|
|
||||||
|
// 8 bytes of feature specific data.
|
||||||
|
// The first 6 bytes must be "empty".
|
||||||
|
// The last 2 bytes is the relative height as big-endian u16.
|
||||||
|
reader.read_empty_bytes(6)?;
|
||||||
|
let relative_height = NRDRelativeHeight::read(reader)?;
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee,
|
||||||
|
relative_height,
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ser::Error::CorruptedData);
|
return Err(ser::Error::CorruptedData);
|
||||||
}
|
}
|
||||||
|
@ -176,6 +304,19 @@ impl KernelFeatures {
|
||||||
let lock_height = reader.read_u64()?;
|
let lock_height = reader.read_u64()?;
|
||||||
KernelFeatures::HeightLocked { fee, lock_height }
|
KernelFeatures::HeightLocked { fee, lock_height }
|
||||||
}
|
}
|
||||||
|
KernelFeatures::NO_RECENT_DUPLICATE_U8 => {
|
||||||
|
// NRD kernels are invalid if NRD feature flag is not enabled.
|
||||||
|
if !global::is_nrd_enabled() {
|
||||||
|
return Err(ser::Error::CorruptedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fee = reader.read_u64()?;
|
||||||
|
let relative_height = NRDRelativeHeight::read(reader)?;
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee,
|
||||||
|
relative_height,
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ser::Error::CorruptedData);
|
return Err(ser::Error::CorruptedData);
|
||||||
}
|
}
|
||||||
|
@ -246,6 +387,8 @@ pub enum Error {
|
||||||
/// Validation error relating to kernel features.
|
/// Validation error relating to kernel features.
|
||||||
/// It is invalid for a transaction to contain a coinbase kernel, for example.
|
/// It is invalid for a transaction to contain a coinbase kernel, for example.
|
||||||
InvalidKernelFeatures,
|
InvalidKernelFeatures,
|
||||||
|
/// NRD kernel relative height is limited to 1 week duration and must be greater than 0.
|
||||||
|
InvalidNRDRelativeHeight,
|
||||||
/// Signature verification error.
|
/// Signature verification error.
|
||||||
IncorrectSignature,
|
IncorrectSignature,
|
||||||
/// Underlying serialization error.
|
/// Underlying serialization error.
|
||||||
|
@ -383,6 +526,14 @@ impl KernelFeatures {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this an NRD kernel?
|
||||||
|
pub fn is_nrd(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
KernelFeatures::NoRecentDuplicate { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TxKernel {
|
impl TxKernel {
|
||||||
|
@ -401,6 +552,11 @@ impl TxKernel {
|
||||||
self.features.is_height_locked()
|
self.features.is_height_locked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is this an NRD kernel?
|
||||||
|
pub fn is_nrd(&self) -> bool {
|
||||||
|
self.features.is_nrd()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the excess commitment for this tx_kernel.
|
/// Return the excess commitment for this tx_kernel.
|
||||||
pub fn excess(&self) -> Commitment {
|
pub fn excess(&self) -> Commitment {
|
||||||
self.excess
|
self.excess
|
||||||
|
@ -422,14 +578,13 @@ impl TxKernel {
|
||||||
let sig = &self.excess_sig;
|
let sig = &self.excess_sig;
|
||||||
// Verify aggsig directly in libsecp
|
// Verify aggsig directly in libsecp
|
||||||
let pubkey = &self.excess.to_pubkey(&secp)?;
|
let pubkey = &self.excess.to_pubkey(&secp)?;
|
||||||
if !secp::aggsig::verify_single(
|
if !aggsig::verify_single(
|
||||||
&secp,
|
&secp,
|
||||||
&sig,
|
&sig,
|
||||||
&self.msg_to_sign()?,
|
&self.msg_to_sign()?,
|
||||||
None,
|
None,
|
||||||
&pubkey,
|
&pubkey,
|
||||||
Some(&pubkey),
|
Some(&pubkey),
|
||||||
None,
|
|
||||||
false,
|
false,
|
||||||
) {
|
) {
|
||||||
return Err(Error::IncorrectSignature);
|
return Err(Error::IncorrectSignature);
|
||||||
|
@ -440,9 +595,9 @@ impl TxKernel {
|
||||||
/// Batch signature verification.
|
/// Batch signature verification.
|
||||||
pub fn batch_sig_verify(tx_kernels: &[TxKernel]) -> Result<(), Error> {
|
pub fn batch_sig_verify(tx_kernels: &[TxKernel]) -> Result<(), Error> {
|
||||||
let len = tx_kernels.len();
|
let len = tx_kernels.len();
|
||||||
let mut sigs: Vec<secp::Signature> = Vec::with_capacity(len);
|
let mut sigs = Vec::with_capacity(len);
|
||||||
let mut pubkeys: Vec<secp::key::PublicKey> = Vec::with_capacity(len);
|
let mut pubkeys = Vec::with_capacity(len);
|
||||||
let mut msgs: Vec<secp::Message> = Vec::with_capacity(len);
|
let mut msgs = Vec::with_capacity(len);
|
||||||
|
|
||||||
let secp = static_secp_instance();
|
let secp = static_secp_instance();
|
||||||
let secp = secp.lock();
|
let secp = secp.lock();
|
||||||
|
@ -453,7 +608,7 @@ impl TxKernel {
|
||||||
msgs.push(tx_kernel.msg_to_sign()?);
|
msgs.push(tx_kernel.msg_to_sign()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !secp::aggsig::verify_batch(&secp, &sigs, &msgs, &pubkeys) {
|
if !aggsig::verify_batch(&secp, &sigs, &msgs, &pubkeys) {
|
||||||
return Err(Error::IncorrectSignature);
|
return Err(Error::IncorrectSignature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,9 +823,9 @@ impl TransactionBody {
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|k| match k.features {
|
.filter_map(|k| match k.features {
|
||||||
KernelFeatures::Coinbase => None,
|
KernelFeatures::Coinbase => None,
|
||||||
KernelFeatures::Plain { fee } | KernelFeatures::HeightLocked { fee, .. } => {
|
KernelFeatures::Plain { fee } => Some(fee),
|
||||||
Some(fee)
|
KernelFeatures::HeightLocked { fee, .. } => Some(fee),
|
||||||
}
|
KernelFeatures::NoRecentDuplicate { fee, .. } => Some(fee),
|
||||||
})
|
})
|
||||||
.fold(0, |acc, fee| acc.saturating_add(fee))
|
.fold(0, |acc, fee| acc.saturating_add(fee))
|
||||||
}
|
}
|
||||||
|
@ -1577,10 +1732,9 @@ mod test {
|
||||||
use crate::core::hash::Hash;
|
use crate::core::hash::Hash;
|
||||||
use crate::core::id::{ShortId, ShortIdentifiable};
|
use crate::core::id::{ShortId, ShortIdentifiable};
|
||||||
use keychain::{ExtKeychain, Keychain, SwitchCommitmentType};
|
use keychain::{ExtKeychain, Keychain, SwitchCommitmentType};
|
||||||
use util::secp;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_kernel_ser_deser() {
|
fn test_plain_kernel_ser_deser() {
|
||||||
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||||
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
||||||
let commit = keychain
|
let commit = keychain
|
||||||
|
@ -1596,12 +1750,35 @@ mod test {
|
||||||
excess_sig: sig.clone(),
|
excess_sig: sig.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Test explicit protocol version.
|
||||||
|
for version in vec![ProtocolVersion(1), ProtocolVersion(2)] {
|
||||||
|
let mut vec = vec![];
|
||||||
|
ser::serialize(&mut vec, version, &kernel).expect("serialized failed");
|
||||||
|
let kernel2: TxKernel = ser::deserialize(&mut &vec[..], version).unwrap();
|
||||||
|
assert_eq!(kernel2.features, KernelFeatures::Plain { fee: 10 });
|
||||||
|
assert_eq!(kernel2.excess, commit);
|
||||||
|
assert_eq!(kernel2.excess_sig, sig.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with "default" protocol version.
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
|
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
|
||||||
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
|
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||||
assert_eq!(kernel2.features, KernelFeatures::Plain { fee: 10 });
|
assert_eq!(kernel2.features, KernelFeatures::Plain { fee: 10 });
|
||||||
assert_eq!(kernel2.excess, commit);
|
assert_eq!(kernel2.excess, commit);
|
||||||
assert_eq!(kernel2.excess_sig, sig.clone());
|
assert_eq!(kernel2.excess_sig, sig.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_height_locked_kernel_ser_deser() {
|
||||||
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||||
|
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
||||||
|
let commit = keychain
|
||||||
|
.commit(5, &key_id, SwitchCommitmentType::Regular)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// just some bytes for testing ser/deser
|
||||||
|
let sig = secp::Signature::from_raw_data(&[0; 64]).unwrap();
|
||||||
|
|
||||||
// now check a kernel with lock_height serialize/deserialize correctly
|
// now check a kernel with lock_height serialize/deserialize correctly
|
||||||
let kernel = TxKernel {
|
let kernel = TxKernel {
|
||||||
|
@ -1613,20 +1790,123 @@ mod test {
|
||||||
excess_sig: sig.clone(),
|
excess_sig: sig.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Test explicit protocol version.
|
||||||
|
for version in vec![ProtocolVersion(1), ProtocolVersion(2)] {
|
||||||
|
let mut vec = vec![];
|
||||||
|
ser::serialize(&mut vec, version, &kernel).expect("serialized failed");
|
||||||
|
let kernel2: TxKernel = ser::deserialize(&mut &vec[..], version).unwrap();
|
||||||
|
assert_eq!(kernel.features, kernel2.features);
|
||||||
|
assert_eq!(kernel2.excess, commit);
|
||||||
|
assert_eq!(kernel2.excess_sig, sig.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with "default" protocol version.
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
|
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
|
||||||
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
|
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(kernel.features, kernel2.features);
|
||||||
kernel2.features,
|
|
||||||
KernelFeatures::HeightLocked {
|
|
||||||
fee: 10,
|
|
||||||
lock_height: 100
|
|
||||||
}
|
|
||||||
);
|
|
||||||
assert_eq!(kernel2.excess, commit);
|
assert_eq!(kernel2.excess, commit);
|
||||||
assert_eq!(kernel2.excess_sig, sig.clone());
|
assert_eq!(kernel2.excess_sig, sig.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nrd_kernel_ser_deser() {
|
||||||
|
global::set_local_nrd_enabled(true);
|
||||||
|
|
||||||
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||||
|
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
||||||
|
let commit = keychain
|
||||||
|
.commit(5, &key_id, SwitchCommitmentType::Regular)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// just some bytes for testing ser/deser
|
||||||
|
let sig = secp::Signature::from_raw_data(&[0; 64]).unwrap();
|
||||||
|
|
||||||
|
// now check an NRD kernel will serialize/deserialize correctly
|
||||||
|
let kernel = TxKernel {
|
||||||
|
features: KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 10,
|
||||||
|
relative_height: NRDRelativeHeight(100),
|
||||||
|
},
|
||||||
|
excess: commit,
|
||||||
|
excess_sig: sig.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test explicit protocol version.
|
||||||
|
for version in vec![ProtocolVersion(1), ProtocolVersion(2)] {
|
||||||
|
let mut vec = vec![];
|
||||||
|
ser::serialize(&mut vec, version, &kernel).expect("serialized failed");
|
||||||
|
let kernel2: TxKernel = ser::deserialize(&mut &vec[..], version).unwrap();
|
||||||
|
assert_eq!(kernel.features, kernel2.features);
|
||||||
|
assert_eq!(kernel2.excess, commit);
|
||||||
|
assert_eq!(kernel2.excess_sig, sig.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with "default" protocol version.
|
||||||
|
let mut vec = vec![];
|
||||||
|
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
|
||||||
|
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||||
|
assert_eq!(kernel.features, kernel2.features);
|
||||||
|
assert_eq!(kernel2.excess, commit);
|
||||||
|
assert_eq!(kernel2.excess_sig, sig.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nrd_kernel_verify_sig() {
|
||||||
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||||
|
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 10,
|
||||||
|
relative_height: NRDRelativeHeight(100),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Construct the message to be signed.
|
||||||
|
let msg = kernel.msg_to_sign().unwrap();
|
||||||
|
|
||||||
|
let excess = keychain
|
||||||
|
.commit(0, &key_id, SwitchCommitmentType::Regular)
|
||||||
|
.unwrap();
|
||||||
|
let skey = keychain
|
||||||
|
.derive_key(0, &key_id, SwitchCommitmentType::Regular)
|
||||||
|
.unwrap();
|
||||||
|
let pubkey = excess.to_pubkey(&keychain.secp()).unwrap();
|
||||||
|
|
||||||
|
let excess_sig =
|
||||||
|
aggsig::sign_single(&keychain.secp(), &msg, &skey, None, Some(&pubkey)).unwrap();
|
||||||
|
|
||||||
|
kernel.excess = excess;
|
||||||
|
kernel.excess_sig = excess_sig;
|
||||||
|
|
||||||
|
// Check the signature verifies.
|
||||||
|
assert_eq!(kernel.verify(), Ok(()));
|
||||||
|
|
||||||
|
// Modify the fee and check signature no longer verifies.
|
||||||
|
kernel.features = KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 9,
|
||||||
|
relative_height: NRDRelativeHeight(100),
|
||||||
|
};
|
||||||
|
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
|
||||||
|
|
||||||
|
// Modify the relative_height and check signature no longer verifies.
|
||||||
|
kernel.features = KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 10,
|
||||||
|
relative_height: NRDRelativeHeight(101),
|
||||||
|
};
|
||||||
|
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
|
||||||
|
|
||||||
|
// Swap the features out for something different and check signature no longer verifies.
|
||||||
|
kernel.features = KernelFeatures::Plain { fee: 10 };
|
||||||
|
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
|
||||||
|
|
||||||
|
// Check signature verifies if we use the original features.
|
||||||
|
kernel.features = KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 10,
|
||||||
|
relative_height: NRDRelativeHeight(100),
|
||||||
|
};
|
||||||
|
assert_eq!(kernel.verify(), Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn commit_consistency() {
|
fn commit_consistency() {
|
||||||
let keychain = ExtKeychain::from_seed(&[0; 32], false).unwrap();
|
let keychain = ExtKeychain::from_seed(&[0; 32], false).unwrap();
|
||||||
|
@ -1678,20 +1958,20 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn kernel_features_serialization() {
|
fn kernel_features_serialization() -> Result<(), Error> {
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
ser::serialize_default(&mut vec, &(0u8, 10u64, 0u64)).expect("serialized failed");
|
ser::serialize_default(&mut vec, &(0u8, 10u64, 0u64))?;
|
||||||
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..]).unwrap();
|
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
|
||||||
assert_eq!(features, KernelFeatures::Plain { fee: 10 });
|
assert_eq!(features, KernelFeatures::Plain { fee: 10 });
|
||||||
|
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
ser::serialize_default(&mut vec, &(1u8, 0u64, 0u64)).expect("serialized failed");
|
ser::serialize_default(&mut vec, &(1u8, 0u64, 0u64))?;
|
||||||
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..]).unwrap();
|
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
|
||||||
assert_eq!(features, KernelFeatures::Coinbase);
|
assert_eq!(features, KernelFeatures::Coinbase);
|
||||||
|
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
ser::serialize_default(&mut vec, &(2u8, 10u64, 100u64)).expect("serialized failed");
|
ser::serialize_default(&mut vec, &(2u8, 10u64, 100u64))?;
|
||||||
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..]).unwrap();
|
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
features,
|
features,
|
||||||
KernelFeatures::HeightLocked {
|
KernelFeatures::HeightLocked {
|
||||||
|
@ -1700,9 +1980,55 @@ mod test {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// NRD kernel support not enabled by default.
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
ser::serialize_default(&mut vec, &(3u8, 0u64, 0u64)).expect("serialized failed");
|
ser::serialize_default(&mut vec, &(3u8, 10u64, 100u16)).expect("serialized failed");
|
||||||
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
|
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
|
||||||
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
|
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
|
||||||
|
|
||||||
|
// Additional kernel features unsupported.
|
||||||
|
let mut vec = vec![];
|
||||||
|
ser::serialize_default(&mut vec, &(4u8)).expect("serialized failed");
|
||||||
|
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
|
||||||
|
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn kernel_features_serialization_nrd_enabled() -> Result<(), Error> {
|
||||||
|
global::set_local_nrd_enabled(true);
|
||||||
|
|
||||||
|
let mut vec = vec![];
|
||||||
|
ser::serialize_default(&mut vec, &(3u8, 10u64, 100u16))?;
|
||||||
|
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
|
||||||
|
assert_eq!(
|
||||||
|
features,
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 10,
|
||||||
|
relative_height: NRDRelativeHeight(100)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// NRD with relative height 0 is invalid.
|
||||||
|
vec.clear();
|
||||||
|
ser::serialize_default(&mut vec, &(3u8, 10u64, 0u16))?;
|
||||||
|
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
|
||||||
|
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
|
||||||
|
|
||||||
|
// NRD with relative height WEEK_HEIGHT+1 is invalid.
|
||||||
|
vec.clear();
|
||||||
|
let invalid_height = consensus::WEEK_HEIGHT + 1;
|
||||||
|
ser::serialize_default(&mut vec, &(3u8, 10u64, invalid_height as u16))?;
|
||||||
|
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
|
||||||
|
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
|
||||||
|
|
||||||
|
// Kernel variant 4 (and above) is invalid.
|
||||||
|
let mut vec = vec![];
|
||||||
|
ser::serialize_default(&mut vec, &(4u8))?;
|
||||||
|
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
|
||||||
|
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,11 +140,19 @@ lazy_static! {
|
||||||
/// This is accessed via get_chain_type() which allows the global value
|
/// This is accessed via get_chain_type() which allows the global value
|
||||||
/// to be overridden on a per-thread basis (for testing).
|
/// to be overridden on a per-thread basis (for testing).
|
||||||
pub static ref GLOBAL_CHAIN_TYPE: OneTime<ChainTypes> = OneTime::new();
|
pub static ref GLOBAL_CHAIN_TYPE: OneTime<ChainTypes> = OneTime::new();
|
||||||
|
|
||||||
|
/// Global feature flag for NRD kernel support.
|
||||||
|
/// If enabled NRD kernels are treated as valid after HF3 (based on header version).
|
||||||
|
/// If disabled NRD kernels are invalid regardless of header version or block height.
|
||||||
|
pub static ref GLOBAL_NRD_FEATURE_ENABLED: OneTime<bool> = OneTime::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
/// Mainnet|Floonet|UserTesting|AutomatedTesting
|
/// Mainnet|Floonet|UserTesting|AutomatedTesting
|
||||||
pub static CHAIN_TYPE: Cell<Option<ChainTypes>> = Cell::new(None);
|
pub static CHAIN_TYPE: Cell<Option<ChainTypes>> = Cell::new(None);
|
||||||
|
|
||||||
|
/// Local feature flag for NRD kernel support.
|
||||||
|
pub static NRD_FEATURE_ENABLED: Cell<Option<bool>> = Cell::new(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the chain type on a per-thread basis via thread_local storage.
|
/// Set the chain type on a per-thread basis via thread_local storage.
|
||||||
|
@ -174,6 +182,36 @@ pub fn init_global_chain_type(new_type: ChainTypes) {
|
||||||
GLOBAL_CHAIN_TYPE.init(new_type)
|
GLOBAL_CHAIN_TYPE.init(new_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One time initialization of the global chain_type.
|
||||||
|
/// Will panic if we attempt to re-initialize this (via OneTime).
|
||||||
|
pub fn init_global_nrd_enabled(enabled: bool) {
|
||||||
|
GLOBAL_NRD_FEATURE_ENABLED.init(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicitly enable the NRD global feature flag.
|
||||||
|
pub fn set_local_nrd_enabled(enabled: bool) {
|
||||||
|
NRD_FEATURE_ENABLED.with(|flag| flag.set(Some(enabled)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is the NRD feature flag enabled?
|
||||||
|
/// Look at thread local config first. If not set fallback to global config.
|
||||||
|
/// Default to false if global config unset.
|
||||||
|
pub fn is_nrd_enabled() -> bool {
|
||||||
|
NRD_FEATURE_ENABLED.with(|flag| match flag.get() {
|
||||||
|
None => {
|
||||||
|
if GLOBAL_NRD_FEATURE_ENABLED.is_init() {
|
||||||
|
let global_flag = GLOBAL_NRD_FEATURE_ENABLED.borrow();
|
||||||
|
flag.set(Some(global_flag));
|
||||||
|
global_flag
|
||||||
|
} else {
|
||||||
|
// Global config unset, default to false.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(flag) => flag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Return either a cuckoo context or a cuckatoo context
|
/// Return either a cuckoo context or a cuckatoo context
|
||||||
/// Single change point
|
/// Single change point
|
||||||
pub fn create_pow_context<T>(
|
pub fn create_pow_context<T>(
|
||||||
|
|
|
@ -441,6 +441,16 @@ pub fn verify_single(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify a batch of signatures.
|
||||||
|
pub fn verify_batch(
|
||||||
|
secp: &Secp256k1,
|
||||||
|
sigs: &Vec<Signature>,
|
||||||
|
msgs: &Vec<Message>,
|
||||||
|
pubkeys: &Vec<PublicKey>,
|
||||||
|
) -> bool {
|
||||||
|
aggsig::verify_batch(secp, sigs, msgs, pubkeys)
|
||||||
|
}
|
||||||
|
|
||||||
/// Just a simple sig, creates its own nonce, etc
|
/// Just a simple sig, creates its own nonce, etc
|
||||||
pub fn sign_with_blinding(
|
pub fn sign_with_blinding(
|
||||||
secp: &Secp256k1,
|
secp: &Secp256k1,
|
||||||
|
@ -449,7 +459,6 @@ pub fn sign_with_blinding(
|
||||||
pubkey_sum: Option<&PublicKey>,
|
pubkey_sum: Option<&PublicKey>,
|
||||||
) -> Result<Signature, Error> {
|
) -> Result<Signature, Error> {
|
||||||
let skey = &blinding.secret_key(&secp)?;
|
let skey = &blinding.secret_key(&secp)?;
|
||||||
//let pubkey_sum = PublicKey::from_secret_key(&secp, &skey)?;
|
|
||||||
let sig = aggsig::sign_single(secp, &msg, skey, None, None, None, pubkey_sum, None)?;
|
let sig = aggsig::sign_single(secp, &msg, skey, None, None, None, pubkey_sum, None)?;
|
||||||
Ok(sig)
|
Ok(sig)
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,6 +186,11 @@ pub trait Writer {
|
||||||
|
|
||||||
/// Writes a fixed number of bytes. The reader is expected to know the actual length on read.
|
/// Writes a fixed number of bytes. The reader is expected to know the actual length on read.
|
||||||
fn write_fixed_bytes<T: AsRef<[u8]>>(&mut self, bytes: T) -> Result<(), Error>;
|
fn write_fixed_bytes<T: AsRef<[u8]>>(&mut self, bytes: T) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Writes a fixed length of "empty" bytes.
|
||||||
|
fn write_empty_bytes(&mut self, length: usize) -> Result<(), Error> {
|
||||||
|
self.write_fixed_bytes(vec![0u8; length])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementations defined how different numbers and binary structures are
|
/// Implementations defined how different numbers and binary structures are
|
||||||
|
@ -213,6 +218,17 @@ pub trait Reader {
|
||||||
/// Access to underlying protocol version to support
|
/// Access to underlying protocol version to support
|
||||||
/// version specific deserialization logic.
|
/// version specific deserialization logic.
|
||||||
fn protocol_version(&self) -> ProtocolVersion;
|
fn protocol_version(&self) -> ProtocolVersion;
|
||||||
|
|
||||||
|
/// Read a fixed number of "empty" bytes from the underlying reader.
|
||||||
|
/// It is an error if any non-empty bytes encountered.
|
||||||
|
fn read_empty_bytes(&mut self, length: usize) -> Result<(), Error> {
|
||||||
|
for _ in 0..length {
|
||||||
|
if self.read_u8()? != 0u8 {
|
||||||
|
return Err(Error::CorruptedData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait that every type that can be serialized as binary must implement.
|
/// Trait that every type that can be serialized as binary must implement.
|
||||||
|
|
|
@ -14,16 +14,15 @@
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
use crate::common::{new_block, tx1i2o, tx2i1o, txspend1i1o};
|
use crate::common::{new_block, tx1i2o, tx2i1o, txspend1i1o};
|
||||||
use crate::core::consensus::BLOCK_OUTPUT_WEIGHT;
|
use crate::core::consensus::{self, BLOCK_OUTPUT_WEIGHT, TESTING_THIRD_HARD_FORK};
|
||||||
use crate::core::core::block::Error;
|
use crate::core::core::block::{Block, BlockHeader, Error, HeaderVersion};
|
||||||
use crate::core::core::hash::Hashed;
|
use crate::core::core::hash::Hashed;
|
||||||
use crate::core::core::id::ShortIdentifiable;
|
use crate::core::core::id::ShortIdentifiable;
|
||||||
use crate::core::core::transaction::{self, Transaction};
|
use crate::core::core::transaction::{
|
||||||
use crate::core::core::verifier_cache::{LruVerifierCache, VerifierCache};
|
self, KernelFeatures, NRDRelativeHeight, OutputFeatures, Transaction,
|
||||||
use crate::core::core::Committed;
|
|
||||||
use crate::core::core::{
|
|
||||||
Block, BlockHeader, CompactBlock, HeaderVersion, KernelFeatures, OutputFeatures,
|
|
||||||
};
|
};
|
||||||
|
use crate::core::core::verifier_cache::{LruVerifierCache, VerifierCache};
|
||||||
|
use crate::core::core::{Committed, CompactBlock};
|
||||||
use crate::core::libtx::build::{self, input, output};
|
use crate::core::libtx::build::{self, input, output};
|
||||||
use crate::core::libtx::ProofBuilder;
|
use crate::core::libtx::ProofBuilder;
|
||||||
use crate::core::{global, ser};
|
use crate::core::{global, ser};
|
||||||
|
@ -84,6 +83,178 @@ fn very_empty_block() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_with_nrd_kernel_pre_post_hf3() {
|
||||||
|
// automated testing - HF{1|2|3} at block heights {3, 6, 9}
|
||||||
|
// Enable the global NRD feature flag. NRD kernels valid at HF3 at height 9.
|
||||||
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_nrd_enabled(true);
|
||||||
|
|
||||||
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||||
|
let builder = ProofBuilder::new(&keychain);
|
||||||
|
let key_id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
||||||
|
let key_id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0);
|
||||||
|
|
||||||
|
let mut tx = build::transaction(
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 2,
|
||||||
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
|
},
|
||||||
|
vec![input(7, key_id1), output(5, key_id2)],
|
||||||
|
&keychain,
|
||||||
|
&builder,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let prev_height = TESTING_THIRD_HARD_FORK - 2;
|
||||||
|
let prev = BlockHeader {
|
||||||
|
height: prev_height,
|
||||||
|
version: consensus::header_version(prev_height),
|
||||||
|
..BlockHeader::default()
|
||||||
|
};
|
||||||
|
let b = new_block(
|
||||||
|
vec![&mut tx],
|
||||||
|
&keychain,
|
||||||
|
&builder,
|
||||||
|
&prev,
|
||||||
|
&ExtKeychain::derive_key_id(1, 1, 0, 0, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Block is invalid at header version 3 if it contains an NRD kernel.
|
||||||
|
assert_eq!(b.header.version, HeaderVersion(3));
|
||||||
|
assert_eq!(
|
||||||
|
b.validate(&BlindingFactor::zero(), verifier_cache()),
|
||||||
|
Err(Error::NRDKernelPreHF3)
|
||||||
|
);
|
||||||
|
|
||||||
|
let prev_height = TESTING_THIRD_HARD_FORK - 1;
|
||||||
|
let prev = BlockHeader {
|
||||||
|
height: prev_height,
|
||||||
|
version: consensus::header_version(prev_height),
|
||||||
|
..BlockHeader::default()
|
||||||
|
};
|
||||||
|
let b = new_block(
|
||||||
|
vec![&mut tx],
|
||||||
|
&keychain,
|
||||||
|
&builder,
|
||||||
|
&prev,
|
||||||
|
&ExtKeychain::derive_key_id(1, 1, 0, 0, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Block is valid at header version 4 (at HF height) if it contains an NRD kernel.
|
||||||
|
assert_eq!(b.header.height, TESTING_THIRD_HARD_FORK);
|
||||||
|
assert_eq!(b.header.version, HeaderVersion(4));
|
||||||
|
assert!(b
|
||||||
|
.validate(&BlindingFactor::zero(), verifier_cache())
|
||||||
|
.is_ok());
|
||||||
|
|
||||||
|
let prev_height = TESTING_THIRD_HARD_FORK;
|
||||||
|
let prev = BlockHeader {
|
||||||
|
height: prev_height,
|
||||||
|
version: consensus::header_version(prev_height),
|
||||||
|
..BlockHeader::default()
|
||||||
|
};
|
||||||
|
let b = new_block(
|
||||||
|
vec![&mut tx],
|
||||||
|
&keychain,
|
||||||
|
&builder,
|
||||||
|
&prev,
|
||||||
|
&ExtKeychain::derive_key_id(1, 1, 0, 0, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Block is valid at header version 4 if it contains an NRD kernel.
|
||||||
|
assert_eq!(b.header.version, HeaderVersion(4));
|
||||||
|
assert!(b
|
||||||
|
.validate(&BlindingFactor::zero(), verifier_cache())
|
||||||
|
.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_with_nrd_kernel_nrd_not_enabled() {
|
||||||
|
// automated testing - HF{1|2|3} at block heights {3, 6, 9}
|
||||||
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
|
||||||
|
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||||
|
let builder = ProofBuilder::new(&keychain);
|
||||||
|
let key_id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
|
||||||
|
let key_id2 = ExtKeychain::derive_key_id(1, 2, 0, 0, 0);
|
||||||
|
|
||||||
|
let mut tx = build::transaction(
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 2,
|
||||||
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
|
},
|
||||||
|
vec![input(7, key_id1), output(5, key_id2)],
|
||||||
|
&keychain,
|
||||||
|
&builder,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let prev_height = TESTING_THIRD_HARD_FORK - 2;
|
||||||
|
let prev = BlockHeader {
|
||||||
|
height: prev_height,
|
||||||
|
version: consensus::header_version(prev_height),
|
||||||
|
..BlockHeader::default()
|
||||||
|
};
|
||||||
|
let b = new_block(
|
||||||
|
vec![&mut tx],
|
||||||
|
&keychain,
|
||||||
|
&builder,
|
||||||
|
&prev,
|
||||||
|
&ExtKeychain::derive_key_id(1, 1, 0, 0, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Block is invalid as NRD not enabled.
|
||||||
|
assert_eq!(b.header.version, HeaderVersion(3));
|
||||||
|
assert_eq!(
|
||||||
|
b.validate(&BlindingFactor::zero(), verifier_cache()),
|
||||||
|
Err(Error::NRDKernelNotEnabled)
|
||||||
|
);
|
||||||
|
|
||||||
|
let prev_height = TESTING_THIRD_HARD_FORK - 1;
|
||||||
|
let prev = BlockHeader {
|
||||||
|
height: prev_height,
|
||||||
|
version: consensus::header_version(prev_height),
|
||||||
|
..BlockHeader::default()
|
||||||
|
};
|
||||||
|
let b = new_block(
|
||||||
|
vec![&mut tx],
|
||||||
|
&keychain,
|
||||||
|
&builder,
|
||||||
|
&prev,
|
||||||
|
&ExtKeychain::derive_key_id(1, 1, 0, 0, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Block is invalid as NRD not enabled.
|
||||||
|
assert_eq!(b.header.height, TESTING_THIRD_HARD_FORK);
|
||||||
|
assert_eq!(b.header.version, HeaderVersion(4));
|
||||||
|
assert_eq!(
|
||||||
|
b.validate(&BlindingFactor::zero(), verifier_cache()),
|
||||||
|
Err(Error::NRDKernelNotEnabled)
|
||||||
|
);
|
||||||
|
|
||||||
|
let prev_height = TESTING_THIRD_HARD_FORK;
|
||||||
|
let prev = BlockHeader {
|
||||||
|
height: prev_height,
|
||||||
|
version: consensus::header_version(prev_height),
|
||||||
|
..BlockHeader::default()
|
||||||
|
};
|
||||||
|
let b = new_block(
|
||||||
|
vec![&mut tx],
|
||||||
|
&keychain,
|
||||||
|
&builder,
|
||||||
|
&prev,
|
||||||
|
&ExtKeychain::derive_key_id(1, 1, 0, 0, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Block is invalid as NRD not enabled.
|
||||||
|
assert_eq!(b.header.version, HeaderVersion(4));
|
||||||
|
assert_eq!(
|
||||||
|
b.validate(&BlindingFactor::zero(), verifier_cache()),
|
||||||
|
Err(Error::NRDKernelNotEnabled)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// builds a block with a tx spending another and check that cut_through occurred
|
// builds a block with a tx spending another and check that cut_through occurred
|
||||||
fn block_with_cut_through() {
|
fn block_with_cut_through() {
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
use self::core::core::hash::{Hash, Hashed};
|
use self::core::core::hash::{Hash, Hashed};
|
||||||
use self::core::core::id::ShortId;
|
use self::core::core::id::ShortId;
|
||||||
use self::core::core::verifier_cache::VerifierCache;
|
use self::core::core::verifier_cache::VerifierCache;
|
||||||
use self::core::core::{transaction, Block, BlockHeader, Transaction, Weighting};
|
use self::core::core::{transaction, Block, BlockHeader, HeaderVersion, Transaction, Weighting};
|
||||||
|
use self::core::global;
|
||||||
use self::util::RwLock;
|
use self::util::RwLock;
|
||||||
use crate::pool::Pool;
|
use crate::pool::Pool;
|
||||||
use crate::types::{BlockChain, PoolAdapter, PoolConfig, PoolEntry, PoolError, TxSource};
|
use crate::types::{BlockChain, PoolAdapter, PoolConfig, PoolEntry, PoolError, TxSource};
|
||||||
|
@ -132,6 +133,24 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify the tx kernel variants and ensure they can all be accepted to the txpool/stempool
|
||||||
|
/// with respect to current header version.
|
||||||
|
fn verify_kernel_variants(
|
||||||
|
&self,
|
||||||
|
tx: &Transaction,
|
||||||
|
header: &BlockHeader,
|
||||||
|
) -> Result<(), PoolError> {
|
||||||
|
if tx.kernels().iter().any(|k| k.is_nrd()) {
|
||||||
|
if !global::is_nrd_enabled() {
|
||||||
|
return Err(PoolError::NRDKernelNotEnabled);
|
||||||
|
}
|
||||||
|
if header.version < HeaderVersion(4) {
|
||||||
|
return Err(PoolError::NRDKernelPreHF3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Add the given tx to the pool, directing it to either the stempool or
|
/// Add the given tx to the pool, directing it to either the stempool or
|
||||||
/// txpool based on stem flag provided.
|
/// txpool based on stem flag provided.
|
||||||
pub fn add_to_pool(
|
pub fn add_to_pool(
|
||||||
|
@ -147,6 +166,9 @@ where
|
||||||
return Err(PoolError::DuplicateTx);
|
return Err(PoolError::DuplicateTx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check this tx is valid based on current header version.
|
||||||
|
self.verify_kernel_variants(&tx, header)?;
|
||||||
|
|
||||||
// Do we have the capacity to accept this transaction?
|
// Do we have the capacity to accept this transaction?
|
||||||
let acceptability = self.is_acceptable(&tx, stem);
|
let acceptability = self.is_acceptable(&tx, stem);
|
||||||
let mut evict = false;
|
let mut evict = false;
|
||||||
|
|
|
@ -221,6 +221,12 @@ pub enum PoolError {
|
||||||
/// Attempt to add a duplicate tx to the pool.
|
/// Attempt to add a duplicate tx to the pool.
|
||||||
#[fail(display = "Duplicate tx")]
|
#[fail(display = "Duplicate tx")]
|
||||||
DuplicateTx,
|
DuplicateTx,
|
||||||
|
/// NRD kernels will not be accepted by the txpool/stempool pre-HF3.
|
||||||
|
#[fail(display = "NRD kernel pre-HF3")]
|
||||||
|
NRDKernelPreHF3,
|
||||||
|
/// NRD kernels are not valid if disabled locally via "feature flag".
|
||||||
|
#[fail(display = "NRD kernel not enabled")]
|
||||||
|
NRDKernelNotEnabled,
|
||||||
/// Other kinds of error (not yet pulled out into meaningful errors).
|
/// Other kinds of error (not yet pulled out into meaningful errors).
|
||||||
#[fail(display = "General pool error {}", _0)]
|
#[fail(display = "General pool error {}", _0)]
|
||||||
Other(String),
|
Other(String),
|
||||||
|
|
|
@ -216,10 +216,26 @@ where
|
||||||
{
|
{
|
||||||
let input_sum = input_values.iter().sum::<u64>() as i64;
|
let input_sum = input_values.iter().sum::<u64>() as i64;
|
||||||
let output_sum = output_values.iter().sum::<u64>() as i64;
|
let output_sum = output_values.iter().sum::<u64>() as i64;
|
||||||
|
|
||||||
let fees: i64 = input_sum - output_sum;
|
let fees: i64 = input_sum - output_sum;
|
||||||
assert!(fees >= 0);
|
assert!(fees >= 0);
|
||||||
|
|
||||||
|
test_transaction_with_kernel_features(
|
||||||
|
keychain,
|
||||||
|
input_values,
|
||||||
|
output_values,
|
||||||
|
KernelFeatures::Plain { fee: fees as u64 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_transaction_with_kernel_features<K>(
|
||||||
|
keychain: &K,
|
||||||
|
input_values: Vec<u64>,
|
||||||
|
output_values: Vec<u64>,
|
||||||
|
kernel_features: KernelFeatures,
|
||||||
|
) -> Transaction
|
||||||
|
where
|
||||||
|
K: Keychain,
|
||||||
|
{
|
||||||
let mut tx_elements = Vec::new();
|
let mut tx_elements = Vec::new();
|
||||||
|
|
||||||
for input_value in input_values {
|
for input_value in input_values {
|
||||||
|
@ -233,7 +249,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
libtx::build::transaction(
|
libtx::build::transaction(
|
||||||
KernelFeatures::Plain { fee: fees as u64 },
|
kernel_features,
|
||||||
tx_elements,
|
tx_elements,
|
||||||
keychain,
|
keychain,
|
||||||
&libtx::ProofBuilder::new(keychain),
|
&libtx::ProofBuilder::new(keychain),
|
||||||
|
|
216
pool/tests/nrd_kernels.rs
Normal file
216
pool/tests/nrd_kernels.rs
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
// Copyright 2020 The Grin Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
pub mod common;
|
||||||
|
|
||||||
|
use self::core::core::hash::Hashed;
|
||||||
|
use self::core::core::verifier_cache::LruVerifierCache;
|
||||||
|
use self::core::core::{
|
||||||
|
Block, BlockHeader, HeaderVersion, KernelFeatures, NRDRelativeHeight, Transaction,
|
||||||
|
};
|
||||||
|
use self::core::global;
|
||||||
|
use self::core::pow::Difficulty;
|
||||||
|
use self::core::{consensus, libtx};
|
||||||
|
use self::keychain::{ExtKeychain, Keychain};
|
||||||
|
use self::pool::types::PoolError;
|
||||||
|
use self::util::RwLock;
|
||||||
|
use crate::common::*;
|
||||||
|
use grin_core as core;
|
||||||
|
use grin_keychain as keychain;
|
||||||
|
use grin_pool as pool;
|
||||||
|
use grin_util as util;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nrd_kernel_verification_block_version() {
|
||||||
|
util::init_test_logger();
|
||||||
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
global::set_local_nrd_enabled(true);
|
||||||
|
|
||||||
|
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
||||||
|
|
||||||
|
let db_root = ".grin_nrd_kernels";
|
||||||
|
clean_output_dir(db_root.into());
|
||||||
|
|
||||||
|
let mut chain = ChainAdapter::init(db_root.into()).unwrap();
|
||||||
|
|
||||||
|
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||||
|
|
||||||
|
// Initialize the chain/txhashset with an initial block
|
||||||
|
// so we have a non-empty UTXO set.
|
||||||
|
let add_block = |prev_header: BlockHeader, txs: Vec<Transaction>, chain: &mut ChainAdapter| {
|
||||||
|
let height = prev_header.height + 1;
|
||||||
|
let key_id = ExtKeychain::derive_key_id(1, height as u32, 0, 0, 0);
|
||||||
|
let fee = txs.iter().map(|x| x.fee()).sum();
|
||||||
|
let reward = libtx::reward::output(
|
||||||
|
&keychain,
|
||||||
|
&libtx::ProofBuilder::new(&keychain),
|
||||||
|
&key_id,
|
||||||
|
fee,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut block = Block::new(&prev_header, txs, Difficulty::min(), reward).unwrap();
|
||||||
|
|
||||||
|
// Set the prev_root to the prev hash for testing purposes (no MMR to obtain a root from).
|
||||||
|
block.header.prev_root = prev_header.hash();
|
||||||
|
|
||||||
|
chain.update_db_for_block(&block);
|
||||||
|
block
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = add_block(BlockHeader::default(), vec![], &mut chain);
|
||||||
|
let header = block.header;
|
||||||
|
|
||||||
|
// Now create tx to spend that first coinbase (now matured).
|
||||||
|
// Provides us with some useful outputs to test with.
|
||||||
|
let initial_tx = test_transaction_spending_coinbase(&keychain, &header, vec![10, 20, 30, 40]);
|
||||||
|
|
||||||
|
// Mine that initial tx so we can spend it with multiple txs
|
||||||
|
let mut block = add_block(header, vec![initial_tx], &mut chain);
|
||||||
|
let mut header = block.header;
|
||||||
|
|
||||||
|
// Initialize a new pool with our chain adapter.
|
||||||
|
let mut pool = test_setup(Arc::new(chain.clone()), verifier_cache);
|
||||||
|
|
||||||
|
let tx_1 = test_transaction_with_kernel_features(
|
||||||
|
&keychain,
|
||||||
|
vec![10, 20],
|
||||||
|
vec![24],
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 6,
|
||||||
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(header.version < HeaderVersion(4));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
pool.add_to_pool(test_source(), tx_1.clone(), false, &header),
|
||||||
|
Err(PoolError::NRDKernelPreHF3)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now mine several more blocks out to HF3
|
||||||
|
for _ in 0..7 {
|
||||||
|
block = add_block(header, vec![], &mut chain);
|
||||||
|
header = block.header;
|
||||||
|
}
|
||||||
|
assert_eq!(header.height, consensus::TESTING_THIRD_HARD_FORK);
|
||||||
|
assert_eq!(header.version, HeaderVersion(4));
|
||||||
|
|
||||||
|
// Now confirm we can successfully add transaction with NRD kernel to txpool.
|
||||||
|
assert_eq!(
|
||||||
|
pool.add_to_pool(test_source(), tx_1.clone(), false, &header),
|
||||||
|
Ok(()),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(pool.total_size(), 1);
|
||||||
|
|
||||||
|
let txs = pool.prepare_mineable_transactions().unwrap();
|
||||||
|
assert_eq!(txs.len(), 1);
|
||||||
|
|
||||||
|
// Cleanup db directory
|
||||||
|
clean_output_dir(db_root.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nrd_kernel_verification_nrd_disabled() {
|
||||||
|
util::init_test_logger();
|
||||||
|
global::set_local_chain_type(global::ChainTypes::AutomatedTesting);
|
||||||
|
|
||||||
|
let keychain: ExtKeychain = Keychain::from_random_seed(false).unwrap();
|
||||||
|
|
||||||
|
let db_root = ".grin_nrd_kernel_disabled";
|
||||||
|
clean_output_dir(db_root.into());
|
||||||
|
|
||||||
|
let mut chain = ChainAdapter::init(db_root.into()).unwrap();
|
||||||
|
|
||||||
|
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||||
|
|
||||||
|
// Initialize the chain/txhashset with an initial block
|
||||||
|
// so we have a non-empty UTXO set.
|
||||||
|
let add_block = |prev_header: BlockHeader, txs: Vec<Transaction>, chain: &mut ChainAdapter| {
|
||||||
|
let height = prev_header.height + 1;
|
||||||
|
let key_id = ExtKeychain::derive_key_id(1, height as u32, 0, 0, 0);
|
||||||
|
let fee = txs.iter().map(|x| x.fee()).sum();
|
||||||
|
let reward = libtx::reward::output(
|
||||||
|
&keychain,
|
||||||
|
&libtx::ProofBuilder::new(&keychain),
|
||||||
|
&key_id,
|
||||||
|
fee,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut block = Block::new(&prev_header, txs, Difficulty::min(), reward).unwrap();
|
||||||
|
|
||||||
|
// Set the prev_root to the prev hash for testing purposes (no MMR to obtain a root from).
|
||||||
|
block.header.prev_root = prev_header.hash();
|
||||||
|
|
||||||
|
chain.update_db_for_block(&block);
|
||||||
|
block
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = add_block(BlockHeader::default(), vec![], &mut chain);
|
||||||
|
let header = block.header;
|
||||||
|
|
||||||
|
// Now create tx to spend that first coinbase (now matured).
|
||||||
|
// Provides us with some useful outputs to test with.
|
||||||
|
let initial_tx = test_transaction_spending_coinbase(&keychain, &header, vec![10, 20, 30, 40]);
|
||||||
|
|
||||||
|
// Mine that initial tx so we can spend it with multiple txs
|
||||||
|
let mut block = add_block(header, vec![initial_tx], &mut chain);
|
||||||
|
let mut header = block.header;
|
||||||
|
|
||||||
|
// Initialize a new pool with our chain adapter.
|
||||||
|
let mut pool = test_setup(Arc::new(chain.clone()), verifier_cache);
|
||||||
|
|
||||||
|
let tx_1 = test_transaction_with_kernel_features(
|
||||||
|
&keychain,
|
||||||
|
vec![10, 20],
|
||||||
|
vec![24],
|
||||||
|
KernelFeatures::NoRecentDuplicate {
|
||||||
|
fee: 6,
|
||||||
|
relative_height: NRDRelativeHeight::new(1440).unwrap(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(header.version < HeaderVersion(4));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
pool.add_to_pool(test_source(), tx_1.clone(), false, &header),
|
||||||
|
Err(PoolError::NRDKernelNotEnabled)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now mine several more blocks out to HF3
|
||||||
|
for _ in 0..7 {
|
||||||
|
block = add_block(header, vec![], &mut chain);
|
||||||
|
header = block.header;
|
||||||
|
}
|
||||||
|
assert_eq!(header.height, consensus::TESTING_THIRD_HARD_FORK);
|
||||||
|
assert_eq!(header.version, HeaderVersion(4));
|
||||||
|
|
||||||
|
// NRD kernel support not enabled via feature flag, so not valid.
|
||||||
|
assert_eq!(
|
||||||
|
pool.add_to_pool(test_source(), tx_1.clone(), false, &header),
|
||||||
|
Err(PoolError::NRDKernelNotEnabled)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(pool.total_size(), 0);
|
||||||
|
|
||||||
|
let txs = pool.prepare_mineable_transactions().unwrap();
|
||||||
|
assert_eq!(txs.len(), 0);
|
||||||
|
|
||||||
|
// Cleanup db directory
|
||||||
|
clean_output_dir(db_root.into());
|
||||||
|
}
|
|
@ -64,6 +64,10 @@ fn log_build_info() {
|
||||||
debug!("{}", detailed_info);
|
debug!("{}", detailed_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn log_feature_flags() {
|
||||||
|
info!("Feature: NRD kernel enabled: {}", global::is_nrd_enabled());
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let exit_code = real_main();
|
let exit_code = real_main();
|
||||||
std::process::exit(exit_code);
|
std::process::exit(exit_code);
|
||||||
|
@ -140,9 +144,6 @@ fn real_main() -> i32 {
|
||||||
};
|
};
|
||||||
init_logger(Some(logging_config), logs_tx);
|
init_logger(Some(logging_config), logs_tx);
|
||||||
|
|
||||||
// One time initialization of the global chain_type.
|
|
||||||
global::init_global_chain_type(config.members.unwrap().server.chain_type);
|
|
||||||
|
|
||||||
if let Some(file_path) = &config.config_file_path {
|
if let Some(file_path) = &config.config_file_path {
|
||||||
info!(
|
info!(
|
||||||
"Using configuration file at {}",
|
"Using configuration file at {}",
|
||||||
|
@ -154,6 +155,22 @@ fn real_main() -> i32 {
|
||||||
|
|
||||||
log_build_info();
|
log_build_info();
|
||||||
|
|
||||||
|
// Initialize our global chain_type and feature flags (NRD kernel support currently).
|
||||||
|
// These are read via global and not read from config beyond this point.
|
||||||
|
global::init_global_chain_type(config.members.unwrap().server.chain_type);
|
||||||
|
info!("Chain: {:?}", global::get_chain_type());
|
||||||
|
match global::get_chain_type() {
|
||||||
|
global::ChainTypes::Mainnet => {
|
||||||
|
// Set various mainnet specific feature flags.
|
||||||
|
global::init_global_nrd_enabled(false);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Set various non-mainnet feature flags.
|
||||||
|
global::init_global_nrd_enabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log_feature_flags();
|
||||||
|
|
||||||
// Execute subcommand
|
// Execute subcommand
|
||||||
match args.subcommand() {
|
match args.subcommand() {
|
||||||
// server commands and options
|
// server commands and options
|
||||||
|
|
Loading…
Reference in a new issue