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:
Antioch Peverell 2020-05-29 09:56:24 +01:00 committed by GitHub
parent 731528c616
commit 988a05f023
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1097 additions and 77 deletions

View file

@ -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,

View file

@ -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,

View 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);
}

View file

@ -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);

View file

@ -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)
} }
} }
} }

View file

@ -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 {

View file

@ -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(())
} }
} }

View file

@ -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>(

View file

@ -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)
} }

View file

@ -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.

View file

@ -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() {

View file

@ -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;

View file

@ -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),

View file

@ -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
View 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());
}

View file

@ -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