mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 08:51:08 +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::Coinbase => (0, 0),
|
||||
KernelFeatures::HeightLocked { fee, lock_height } => (fee, lock_height),
|
||||
KernelFeatures::NoRecentDuplicate {
|
||||
fee,
|
||||
relative_height,
|
||||
} => (fee, relative_height.into()),
|
||||
};
|
||||
TxKernelPrintable {
|
||||
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).
|
||||
fn genesis_block<K>(keychain: &K) -> Block
|
||||
pub fn genesis_block<K>(keychain: &K) -> Block
|
||||
where
|
||||
K: Keychain,
|
||||
{
|
||||
|
@ -69,6 +69,7 @@ where
|
|||
|
||||
/// 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.
|
||||
#[allow(dead_code)]
|
||||
pub fn mine_chain(dir_name: &str, chain_length: u64) -> Chain {
|
||||
global::set_local_chain_type(ChainTypes::AutomatedTesting);
|
||||
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
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn mine_some_on_top<K>(chain: &mut Chain, chain_length: u64, keychain: &K)
|
||||
where
|
||||
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]
|
||||
fn mine_short_chain() {
|
||||
let chain_dir = ".grin.genesis";
|
||||
let chain_dir = ".grin.short";
|
||||
clean_output_dir(chain_dir);
|
||||
let chain = mine_chain(chain_dir, 4);
|
||||
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
|
||||
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;
|
||||
|
||||
/// AutomatedTesting and UserTesting second hard fork height.
|
||||
/// AutomatedTesting and UserTesting HF2 height.
|
||||
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
|
||||
/// 6 months interval scheduled hard forks for the first 2 years.
|
||||
pub fn header_version(height: u64) -> HeaderVersion {
|
||||
|
@ -162,10 +165,12 @@ pub fn header_version(height: u64) -> HeaderVersion {
|
|||
HeaderVersion(1)
|
||||
} else if height < TESTING_SECOND_HARD_FORK {
|
||||
HeaderVersion(2)
|
||||
} else if height < 3 * HARD_FORK_INTERVAL {
|
||||
} else if height < TESTING_THIRD_HARD_FORK {
|
||||
HeaderVersion(3)
|
||||
} else if height < 4 * HARD_FORK_INTERVAL {
|
||||
HeaderVersion(4)
|
||||
} else {
|
||||
HeaderVersion(hf_interval)
|
||||
HeaderVersion(5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,10 @@ pub enum Error {
|
|||
InvalidPow,
|
||||
/// Kernel not valid due to lock_height exceeding block header height
|
||||
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
|
||||
Transaction(transaction::Error),
|
||||
/// Underlying Secp256k1 error (signature validation or invalid public key
|
||||
|
@ -753,6 +757,7 @@ impl Block {
|
|||
self.body.validate(Weighting::AsBlock, verifier)?;
|
||||
|
||||
self.verify_kernel_lock_heights()?;
|
||||
self.verify_nrd_kernels_for_header_version()?;
|
||||
self.verify_coinbase()?;
|
||||
|
||||
// take the kernel offset for this block (block offset minus previous) and
|
||||
|
@ -802,6 +807,7 @@ impl Block {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// Verify any absolute kernel lock heights.
|
||||
fn verify_kernel_lock_heights(&self) -> Result<(), Error> {
|
||||
for k in &self.body.kernels {
|
||||
// check we have no kernels with lock_heights greater than current height
|
||||
|
@ -814,6 +820,21 @@ impl Block {
|
|||
}
|
||||
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 {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
use crate::core::hash::{DefaultHashable, Hashed};
|
||||
use crate::core::verifier_cache::VerifierCache;
|
||||
use crate::core::{committed, Committed};
|
||||
use crate::libtx::secp_ser;
|
||||
use crate::libtx::{aggsig, secp_ser};
|
||||
use crate::ser::{
|
||||
self, read_multi, PMMRable, ProtocolVersion, Readable, Reader, VerifySortedAndUnique,
|
||||
Writeable, Writer,
|
||||
|
@ -27,7 +27,7 @@ use enum_primitive::FromPrimitive;
|
|||
use keychain::{self, BlindingFactor};
|
||||
use std::cmp::Ordering;
|
||||
use std::cmp::{max, min};
|
||||
use std::convert::TryInto;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::sync::Arc;
|
||||
use std::{error, fmt};
|
||||
use util::secp;
|
||||
|
@ -36,6 +36,70 @@ use util::static_secp_instance;
|
|||
use util::RwLock;
|
||||
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.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum KernelFeatures {
|
||||
|
@ -53,12 +117,20 @@ pub enum KernelFeatures {
|
|||
/// Height locked kernels have lock heights.
|
||||
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 {
|
||||
const PLAIN_U8: u8 = 0;
|
||||
const COINBASE_U8: u8 = 1;
|
||||
const HEIGHT_LOCKED_U8: u8 = 2;
|
||||
const NO_RECENT_DUPLICATE_U8: u8 = 3;
|
||||
|
||||
/// Underlying (u8) value representing this kernel variant.
|
||||
/// This is the first byte when we serialize/deserialize the kernel features.
|
||||
|
@ -67,6 +139,7 @@ impl KernelFeatures {
|
|||
KernelFeatures::Plain { .. } => KernelFeatures::PLAIN_U8,
|
||||
KernelFeatures::Coinbase => KernelFeatures::COINBASE_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::Coinbase => String::from("Coinbase"),
|
||||
KernelFeatures::HeightLocked { .. } => String::from("HeightLocked"),
|
||||
KernelFeatures::NoRecentDuplicate { .. } => String::from("NoRecentDuplicate"),
|
||||
}
|
||||
}
|
||||
|
||||
/// msg = hash(features) for coinbase kernels
|
||||
/// hash(features || fee) for plain kernels
|
||||
/// hash(features || fee || lock_height) for height locked kernels
|
||||
/// msg = hash(features) for coinbase kernels
|
||||
/// hash(features || fee) for plain 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> {
|
||||
let x = self.as_u8();
|
||||
let hash = match self {
|
||||
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::NoRecentDuplicate {
|
||||
fee,
|
||||
relative_height,
|
||||
} => (x, fee, relative_height).hash(),
|
||||
};
|
||||
|
||||
let msg = secp::Message::from_slice(&hash.as_bytes())?;
|
||||
|
@ -97,14 +176,36 @@ impl KernelFeatures {
|
|||
/// Write tx kernel features out in v1 protocol format.
|
||||
/// Always include the fee and lock_height, writing 0 value if unused.
|
||||
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_u64(fee)?;
|
||||
writer.write_u64(lock_height)?;
|
||||
match self {
|
||||
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(())
|
||||
}
|
||||
|
||||
|
@ -113,48 +214,75 @@ impl KernelFeatures {
|
|||
/// Only write fee 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> {
|
||||
writer.write_u8(self.as_u8())?;
|
||||
match self {
|
||||
KernelFeatures::Plain { fee } => {
|
||||
writer.write_u8(self.as_u8())?;
|
||||
// Fee only, no additional data on plain kernels.
|
||||
writer.write_u64(*fee)?;
|
||||
}
|
||||
KernelFeatures::Coinbase => {
|
||||
writer.write_u8(self.as_u8())?;
|
||||
// No additional data.
|
||||
}
|
||||
KernelFeatures::HeightLocked { fee, lock_height } => {
|
||||
writer.write_u8(self.as_u8())?;
|
||||
writer.write_u64(*fee)?;
|
||||
// V2 height locked kernels use 8 bytes for the 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(())
|
||||
}
|
||||
|
||||
// Always read feature byte, 8 bytes for fee and 8 bytes for lock height.
|
||||
// Fee and lock height may be unused for some kernel variants but we need
|
||||
// Always read feature byte, 8 bytes for fee and 8 bytes for additional data
|
||||
// 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.
|
||||
fn read_v1<R: Reader>(reader: &mut R) -> Result<KernelFeatures, ser::Error> {
|
||||
let feature_byte = reader.read_u8()?;
|
||||
let fee = reader.read_u64()?;
|
||||
let lock_height = reader.read_u64()?;
|
||||
|
||||
let features = match feature_byte {
|
||||
KernelFeatures::PLAIN_U8 => {
|
||||
if lock_height != 0 {
|
||||
return Err(ser::Error::CorruptedData);
|
||||
}
|
||||
let fee = reader.read_u64()?;
|
||||
// 8 "empty" bytes as additional data is not used.
|
||||
reader.read_empty_bytes(8)?;
|
||||
KernelFeatures::Plain { fee }
|
||||
}
|
||||
KernelFeatures::COINBASE_U8 => {
|
||||
if fee != 0 {
|
||||
return Err(ser::Error::CorruptedData);
|
||||
}
|
||||
if lock_height != 0 {
|
||||
return Err(ser::Error::CorruptedData);
|
||||
}
|
||||
// 8 "empty" bytes as fee is not used.
|
||||
// 8 "empty" bytes as additional data is not used.
|
||||
reader.read_empty_bytes(16)?;
|
||||
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);
|
||||
}
|
||||
|
@ -176,6 +304,19 @@ impl KernelFeatures {
|
|||
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()?;
|
||||
let relative_height = NRDRelativeHeight::read(reader)?;
|
||||
KernelFeatures::NoRecentDuplicate {
|
||||
fee,
|
||||
relative_height,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ser::Error::CorruptedData);
|
||||
}
|
||||
|
@ -246,6 +387,8 @@ pub enum Error {
|
|||
/// Validation error relating to kernel features.
|
||||
/// It is invalid for a transaction to contain a coinbase kernel, for example.
|
||||
InvalidKernelFeatures,
|
||||
/// NRD kernel relative height is limited to 1 week duration and must be greater than 0.
|
||||
InvalidNRDRelativeHeight,
|
||||
/// Signature verification error.
|
||||
IncorrectSignature,
|
||||
/// Underlying serialization error.
|
||||
|
@ -383,6 +526,14 @@ impl KernelFeatures {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this an NRD kernel?
|
||||
pub fn is_nrd(&self) -> bool {
|
||||
match self {
|
||||
KernelFeatures::NoRecentDuplicate { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TxKernel {
|
||||
|
@ -401,6 +552,11 @@ impl TxKernel {
|
|||
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.
|
||||
pub fn excess(&self) -> Commitment {
|
||||
self.excess
|
||||
|
@ -422,14 +578,13 @@ impl TxKernel {
|
|||
let sig = &self.excess_sig;
|
||||
// Verify aggsig directly in libsecp
|
||||
let pubkey = &self.excess.to_pubkey(&secp)?;
|
||||
if !secp::aggsig::verify_single(
|
||||
if !aggsig::verify_single(
|
||||
&secp,
|
||||
&sig,
|
||||
&self.msg_to_sign()?,
|
||||
None,
|
||||
&pubkey,
|
||||
Some(&pubkey),
|
||||
None,
|
||||
false,
|
||||
) {
|
||||
return Err(Error::IncorrectSignature);
|
||||
|
@ -440,9 +595,9 @@ impl TxKernel {
|
|||
/// Batch signature verification.
|
||||
pub fn batch_sig_verify(tx_kernels: &[TxKernel]) -> Result<(), Error> {
|
||||
let len = tx_kernels.len();
|
||||
let mut sigs: Vec<secp::Signature> = Vec::with_capacity(len);
|
||||
let mut pubkeys: Vec<secp::key::PublicKey> = Vec::with_capacity(len);
|
||||
let mut msgs: Vec<secp::Message> = Vec::with_capacity(len);
|
||||
let mut sigs = Vec::with_capacity(len);
|
||||
let mut pubkeys = Vec::with_capacity(len);
|
||||
let mut msgs = Vec::with_capacity(len);
|
||||
|
||||
let secp = static_secp_instance();
|
||||
let secp = secp.lock();
|
||||
|
@ -453,7 +608,7 @@ impl TxKernel {
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -668,9 +823,9 @@ impl TransactionBody {
|
|||
.iter()
|
||||
.filter_map(|k| match k.features {
|
||||
KernelFeatures::Coinbase => None,
|
||||
KernelFeatures::Plain { fee } | KernelFeatures::HeightLocked { fee, .. } => {
|
||||
Some(fee)
|
||||
}
|
||||
KernelFeatures::Plain { fee } => Some(fee),
|
||||
KernelFeatures::HeightLocked { fee, .. } => Some(fee),
|
||||
KernelFeatures::NoRecentDuplicate { fee, .. } => Some(fee),
|
||||
})
|
||||
.fold(0, |acc, fee| acc.saturating_add(fee))
|
||||
}
|
||||
|
@ -1577,10 +1732,9 @@ mod test {
|
|||
use crate::core::hash::Hash;
|
||||
use crate::core::id::{ShortId, ShortIdentifiable};
|
||||
use keychain::{ExtKeychain, Keychain, SwitchCommitmentType};
|
||||
use util::secp;
|
||||
|
||||
#[test]
|
||||
fn test_kernel_ser_deser() {
|
||||
fn test_plain_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
|
||||
|
@ -1596,12 +1750,35 @@ mod test {
|
|||
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![];
|
||||
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
|
||||
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||
assert_eq!(kernel2.features, KernelFeatures::Plain { fee: 10 });
|
||||
assert_eq!(kernel2.excess, commit);
|
||||
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
|
||||
let kernel = TxKernel {
|
||||
|
@ -1613,20 +1790,123 @@ mod test {
|
|||
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!(
|
||||
kernel2.features,
|
||||
KernelFeatures::HeightLocked {
|
||||
fee: 10,
|
||||
lock_height: 100
|
||||
}
|
||||
);
|
||||
assert_eq!(kernel.features, kernel2.features);
|
||||
assert_eq!(kernel2.excess, commit);
|
||||
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]
|
||||
fn commit_consistency() {
|
||||
let keychain = ExtKeychain::from_seed(&[0; 32], false).unwrap();
|
||||
|
@ -1678,20 +1958,20 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn kernel_features_serialization() {
|
||||
fn kernel_features_serialization() -> Result<(), Error> {
|
||||
let mut vec = vec![];
|
||||
ser::serialize_default(&mut vec, &(0u8, 10u64, 0u64)).expect("serialized failed");
|
||||
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||
ser::serialize_default(&mut vec, &(0u8, 10u64, 0u64))?;
|
||||
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
|
||||
assert_eq!(features, KernelFeatures::Plain { fee: 10 });
|
||||
|
||||
let mut vec = vec![];
|
||||
ser::serialize_default(&mut vec, &(1u8, 0u64, 0u64)).expect("serialized failed");
|
||||
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||
ser::serialize_default(&mut vec, &(1u8, 0u64, 0u64))?;
|
||||
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
|
||||
assert_eq!(features, KernelFeatures::Coinbase);
|
||||
|
||||
let mut vec = vec![];
|
||||
ser::serialize_default(&mut vec, &(2u8, 10u64, 100u64)).expect("serialized failed");
|
||||
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||
ser::serialize_default(&mut vec, &(2u8, 10u64, 100u64))?;
|
||||
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
|
||||
assert_eq!(
|
||||
features,
|
||||
KernelFeatures::HeightLocked {
|
||||
|
@ -1700,9 +1980,55 @@ mod test {
|
|||
}
|
||||
);
|
||||
|
||||
// NRD kernel support not enabled by default.
|
||||
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[..]);
|
||||
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
|
||||
/// to be overridden on a per-thread basis (for testing).
|
||||
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! {
|
||||
/// Mainnet|Floonet|UserTesting|AutomatedTesting
|
||||
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.
|
||||
|
@ -174,6 +182,36 @@ pub fn init_global_chain_type(new_type: ChainTypes) {
|
|||
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
|
||||
/// Single change point
|
||||
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
|
||||
pub fn sign_with_blinding(
|
||||
secp: &Secp256k1,
|
||||
|
@ -449,7 +459,6 @@ pub fn sign_with_blinding(
|
|||
pubkey_sum: Option<&PublicKey>,
|
||||
) -> Result<Signature, Error> {
|
||||
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)?;
|
||||
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.
|
||||
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
|
||||
|
@ -213,6 +218,17 @@ pub trait Reader {
|
|||
/// Access to underlying protocol version to support
|
||||
/// version specific deserialization logic.
|
||||
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.
|
||||
|
|
|
@ -14,16 +14,15 @@
|
|||
|
||||
mod common;
|
||||
use crate::common::{new_block, tx1i2o, tx2i1o, txspend1i1o};
|
||||
use crate::core::consensus::BLOCK_OUTPUT_WEIGHT;
|
||||
use crate::core::core::block::Error;
|
||||
use crate::core::consensus::{self, BLOCK_OUTPUT_WEIGHT, TESTING_THIRD_HARD_FORK};
|
||||
use crate::core::core::block::{Block, BlockHeader, Error, HeaderVersion};
|
||||
use crate::core::core::hash::Hashed;
|
||||
use crate::core::core::id::ShortIdentifiable;
|
||||
use crate::core::core::transaction::{self, Transaction};
|
||||
use crate::core::core::verifier_cache::{LruVerifierCache, VerifierCache};
|
||||
use crate::core::core::Committed;
|
||||
use crate::core::core::{
|
||||
Block, BlockHeader, CompactBlock, HeaderVersion, KernelFeatures, OutputFeatures,
|
||||
use crate::core::core::transaction::{
|
||||
self, KernelFeatures, NRDRelativeHeight, OutputFeatures, Transaction,
|
||||
};
|
||||
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::ProofBuilder;
|
||||
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]
|
||||
// builds a block with a tx spending another and check that cut_through occurred
|
||||
fn block_with_cut_through() {
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
use self::core::core::hash::{Hash, Hashed};
|
||||
use self::core::core::id::ShortId;
|
||||
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 crate::pool::Pool;
|
||||
use crate::types::{BlockChain, PoolAdapter, PoolConfig, PoolEntry, PoolError, TxSource};
|
||||
|
@ -132,6 +133,24 @@ where
|
|||
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
|
||||
/// txpool based on stem flag provided.
|
||||
pub fn add_to_pool(
|
||||
|
@ -147,6 +166,9 @@ where
|
|||
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?
|
||||
let acceptability = self.is_acceptable(&tx, stem);
|
||||
let mut evict = false;
|
||||
|
|
|
@ -221,6 +221,12 @@ pub enum PoolError {
|
|||
/// Attempt to add a duplicate tx to the pool.
|
||||
#[fail(display = "Duplicate tx")]
|
||||
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).
|
||||
#[fail(display = "General pool error {}", _0)]
|
||||
Other(String),
|
||||
|
|
|
@ -216,10 +216,26 @@ where
|
|||
{
|
||||
let input_sum = input_values.iter().sum::<u64>() as i64;
|
||||
let output_sum = output_values.iter().sum::<u64>() as i64;
|
||||
|
||||
let fees: i64 = input_sum - output_sum;
|
||||
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();
|
||||
|
||||
for input_value in input_values {
|
||||
|
@ -233,7 +249,7 @@ where
|
|||
}
|
||||
|
||||
libtx::build::transaction(
|
||||
KernelFeatures::Plain { fee: fees as u64 },
|
||||
kernel_features,
|
||||
tx_elements,
|
||||
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);
|
||||
}
|
||||
|
||||
fn log_feature_flags() {
|
||||
info!("Feature: NRD kernel enabled: {}", global::is_nrd_enabled());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let exit_code = real_main();
|
||||
std::process::exit(exit_code);
|
||||
|
@ -140,9 +144,6 @@ fn real_main() -> i32 {
|
|||
};
|
||||
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 {
|
||||
info!(
|
||||
"Using configuration file at {}",
|
||||
|
@ -154,6 +155,22 @@ fn real_main() -> i32 {
|
|||
|
||||
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
|
||||
match args.subcommand() {
|
||||
// server commands and options
|
||||
|
|
Loading…
Reference in a new issue