diff --git a/chain/tests/mine_simple_chain.rs b/chain/tests/mine_simple_chain.rs index 4b71fb696..2f1b7ba9f 100644 --- a/chain/tests/mine_simple_chain.rs +++ b/chain/tests/mine_simple_chain.rs @@ -15,12 +15,18 @@ use self::chain::types::{NoopAdapter, Tip}; use self::chain::Chain; use self::core::core::hash::Hashed; -use self::core::core::{Block, BlockHeader, KernelFeatures, Transaction}; +use self::core::core::{ + block, transaction, Block, BlockHeader, KernelFeatures, Output, OutputFeatures, Transaction, +}; use self::core::global::ChainTypes; -use self::core::libtx::{self, build, ProofBuilder}; +use self::core::libtx::build::{self, Append}; +use self::core::libtx::proof::{self, ProofBuild}; +use self::core::libtx::{self, Error, ProofBuilder}; use self::core::pow::Difficulty; use self::core::{consensus, global, pow}; -use self::keychain::{ExtKeychain, ExtKeychainPath, Keychain}; +use self::keychain::{ + BlindSum, ExtKeychain, ExtKeychainPath, Identifier, Keychain, SwitchCommitmentType, +}; use self::util::RwLock; use chrono::Duration; use grin_chain as chain; @@ -814,6 +820,148 @@ fn output_header_mappings() { clean_output_dir(".grin_header_for_output"); } +/// Build a negative output. This function must not be used outside of tests. +/// The commitment will be an inversion of the value passed in and the value is +/// subtracted from the sum. +fn build_output_negative(value: u64, key_id: Identifier) -> Box> +where + K: Keychain, + B: ProofBuild, +{ + Box::new( + move |build, acc| -> Result<(Transaction, BlindSum), Error> { + let (tx, sum) = acc?; + + // TODO: proper support for different switch commitment schemes + let switch = SwitchCommitmentType::Regular; + + let commit = build.keychain.commit(value, &key_id, switch)?; + + // invert commitment + let commit = build.keychain.secp().commit_sum(vec![], vec![commit])?; + + eprintln!("Building output: {}, {:?}", value, commit); + + // build a proof with a rangeproof of 0 as a placeholder + // the test will replace this later + let proof = proof::create( + build.keychain, + build.builder, + 0, + &key_id, + switch, + commit, + None, + )?; + + // we return the output and the value is subtracted instead of added + Ok(( + tx.with_output(Output::new(OutputFeatures::Plain, commit, proof)), + sum.sub_key_id(key_id.to_value_path(value)), + )) + }, + ) +} + +/// Test the duplicate rangeproof bug +#[test] +fn test_overflow_cached_rangeproof() { + clean_output_dir(".grin_overflow"); + global::set_local_chain_type(ChainTypes::AutomatedTesting); + + util::init_test_logger(); + { + let chain = init_chain(".grin_overflow", pow::mine_genesis_block().unwrap()); + let prev = chain.head_header().unwrap(); + let kc = ExtKeychain::from_random_seed(false).unwrap(); + let pb = ProofBuilder::new(&kc); + + let mut head = prev; + + // mine the first block and keep track of the block_hash + // so we can spend the coinbase later + let b = prepare_block(&kc, &head, &chain, 2); + + assert!(b.outputs()[0].is_coinbase()); + head = b.header.clone(); + chain + .process_block(b.clone(), chain::Options::SKIP_POW) + .unwrap(); + + // now mine three further blocks + for n in 3..6 { + let b = prepare_block(&kc, &head, &chain, n); + head = b.header.clone(); + chain.process_block(b, chain::Options::SKIP_POW).unwrap(); + } + + // create a few keys for use in txns + let key_id2 = ExtKeychainPath::new(1, 2, 0, 0, 0).to_identifier(); + let key_id30 = ExtKeychainPath::new(1, 30, 0, 0, 0).to_identifier(); + let key_id31 = ExtKeychainPath::new(1, 31, 0, 0, 0).to_identifier(); + let key_id32 = ExtKeychainPath::new(1, 32, 0, 0, 0).to_identifier(); + + // build a regular transaction so we have a rangeproof to copy + let tx1 = build::transaction( + KernelFeatures::Plain { fee: 20000.into() }, + &[ + build::coinbase_input(consensus::REWARD, key_id2.clone()), + build::output(consensus::REWARD - 20000, key_id30.clone()), + ], + &kc, + &pb, + ) + .unwrap(); + + // mine block with tx1 + let next = prepare_block_tx(&kc, &head, &chain, 7, &[tx1.clone()]); + let prev_main = next.header.clone(); + chain + .process_block(next.clone(), chain::Options::SKIP_POW) + .unwrap(); + chain.validate(false).unwrap(); + + // create a second tx that contains a negative output + // and a positive output for 1m grin + let mut tx2 = build::transaction( + KernelFeatures::Plain { fee: 0.into() }, + &[ + build::input(consensus::REWARD - 20000, key_id30.clone()), + build::output( + consensus::REWARD - 20000 + 1_000_000_000_000_000, + key_id31.clone(), + ), + build_output_negative(1_000_000_000_000_000, key_id32.clone()), + ], + &kc, + &pb, + ) + .unwrap(); + + // make sure tx1 only has one output as expected + assert_eq!(tx1.body.outputs.len(), 1); + let last_rp = tx1.body.outputs[0].proof; + + // overwrite all our rangeproofs with the rangeproof from last block + for i in 0..tx2.body.outputs.len() { + tx2.body.outputs[i].proof = last_rp; + } + + let next = prepare_block_tx(&kc, &prev_main, &chain, 8, &[tx2.clone()]); + // process_block fails with verifier_cache disabled or with correct verifier_cache + // implementations + let res = chain.process_block(next, chain::Options::SKIP_POW); + + assert_eq!( + res.unwrap_err().kind(), + chain::ErrorKind::InvalidBlockProof(block::Error::Transaction( + transaction::Error::Secp(util::secp::Error::InvalidRangeProof) + )) + ); + } + clean_output_dir(".grin_overflow"); +} + // Use diff as both diff *and* key_idx for convenience (deterministic private key for test blocks) fn prepare_block(kc: &K, prev: &BlockHeader, chain: &Chain, diff: u64) -> Block where