mirror of
https://github.com/mimblewimble/grin.git
synced 2025-01-20 19:11:08 +03:00
merge master into 1.1.0
This commit is contained in:
commit
12fe928112
37 changed files with 2124 additions and 1132 deletions
347
Cargo.lock
generated
347
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -168,44 +168,34 @@ impl Chain {
|
|||
archive_mode: bool,
|
||||
stop_state: Arc<Mutex<StopState>>,
|
||||
) -> Result<Chain, Error> {
|
||||
let chain = {
|
||||
// Note: We take a lock on the stop_state here and do not release it until
|
||||
// we have finished chain initialization.
|
||||
let stop_state_local = stop_state.clone();
|
||||
let stop_lock = stop_state_local.lock();
|
||||
if stop_lock.is_stopped() {
|
||||
return Err(ErrorKind::Stopped.into());
|
||||
}
|
||||
// Note: We take a lock on the stop_state here and do not release it until
|
||||
// we have finished chain initialization.
|
||||
let stop_state_local = stop_state.clone();
|
||||
let stop_lock = stop_state_local.lock();
|
||||
if stop_lock.is_stopped() {
|
||||
return Err(ErrorKind::Stopped.into());
|
||||
}
|
||||
|
||||
let store = Arc::new(store::ChainStore::new(&db_root)?);
|
||||
let store = Arc::new(store::ChainStore::new(&db_root)?);
|
||||
|
||||
// open the txhashset, creating a new one if necessary
|
||||
let mut txhashset = txhashset::TxHashSet::open(db_root.clone(), store.clone(), None)?;
|
||||
// open the txhashset, creating a new one if necessary
|
||||
let mut txhashset = txhashset::TxHashSet::open(db_root.clone(), store.clone(), None)?;
|
||||
|
||||
setup_head(&genesis, &store, &mut txhashset)?;
|
||||
Chain::log_heads(&store)?;
|
||||
setup_head(&genesis, &store, &mut txhashset)?;
|
||||
Chain::log_heads(&store)?;
|
||||
|
||||
Chain {
|
||||
db_root,
|
||||
store,
|
||||
adapter,
|
||||
orphans: Arc::new(OrphanBlockPool::new()),
|
||||
txhashset: Arc::new(RwLock::new(txhashset)),
|
||||
pow_verifier,
|
||||
verifier_cache,
|
||||
archive_mode,
|
||||
stop_state,
|
||||
genesis: genesis.header.clone(),
|
||||
}
|
||||
};
|
||||
|
||||
// Run chain compaction. Laptops and other intermittent nodes
|
||||
// may not run long enough to trigger daily compaction.
|
||||
// So run it explicitly here on startup (its fast enough to do so).
|
||||
// Note: we release the stop_lock from above as compact also requires a lock.
|
||||
chain.compact()?;
|
||||
|
||||
Ok(chain)
|
||||
Ok(Chain {
|
||||
db_root,
|
||||
store,
|
||||
adapter,
|
||||
orphans: Arc::new(OrphanBlockPool::new()),
|
||||
txhashset: Arc::new(RwLock::new(txhashset)),
|
||||
pow_verifier,
|
||||
verifier_cache,
|
||||
archive_mode,
|
||||
stop_state,
|
||||
genesis: genesis.header.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return our shared txhashset instance.
|
||||
|
@ -1055,6 +1045,26 @@ impl Chain {
|
|||
/// * removes historical blocks and associated data from the db (unless archive mode)
|
||||
///
|
||||
pub fn compact(&self) -> Result<(), Error> {
|
||||
// A node may be restarted multiple times in a short period of time.
|
||||
// We compact at most once per 60 blocks in this situation by comparing
|
||||
// current "head" and "tail" height to our cut-through horizon and
|
||||
// allowing an additional 60 blocks in height before allowing a further compaction.
|
||||
if let (Ok(tail), Ok(head)) = (self.tail(), self.head()) {
|
||||
let horizon = global::cut_through_horizon() as u64;
|
||||
let threshold = horizon.saturating_add(60);
|
||||
debug!(
|
||||
"compact: head: {}, tail: {}, diff: {}, horizon: {}",
|
||||
head.height,
|
||||
tail.height,
|
||||
head.height.saturating_sub(tail.height),
|
||||
horizon
|
||||
);
|
||||
if tail.height.saturating_add(threshold) > head.height {
|
||||
debug!("compact: skipping compaction - threshold is 60 blocks beyond horizon.");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Note: We take a lock on the stop_state here and do not release it until
|
||||
// we have finished processing this chain compaction operation.
|
||||
// We want to avoid shutting the node down in the middle of compacting the data.
|
||||
|
|
|
@ -107,6 +107,8 @@ fn data_files() {
|
|||
let chain = reload_chain(chain_dir);
|
||||
chain.validate(false).unwrap();
|
||||
}
|
||||
// Cleanup chain directory
|
||||
clean_output_dir(chain_dir);
|
||||
}
|
||||
|
||||
fn _prepare_block(kc: &ExtKeychain, prev: &BlockHeader, chain: &Chain, diff: u64) -> Block {
|
||||
|
|
|
@ -56,7 +56,11 @@ fn setup(dir_name: &str, genesis: Block) -> Chain {
|
|||
fn mine_empty_chain() {
|
||||
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
||||
let keychain = keychain::ExtKeychain::from_random_seed(false).unwrap();
|
||||
mine_some_on_top(".grin", pow::mine_genesis_block().unwrap(), &keychain);
|
||||
{
|
||||
mine_some_on_top(".grin", pow::mine_genesis_block().unwrap(), &keychain);
|
||||
}
|
||||
// Cleanup chain directory
|
||||
clean_output_dir(".grin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -70,9 +74,10 @@ fn mine_genesis_reward_chain() {
|
|||
let reward = reward::output(&keychain, &key_id, 0, false).unwrap();
|
||||
genesis = genesis.with_reward(reward.0, reward.1);
|
||||
|
||||
let tmp_chain_dir = ".grin.tmp";
|
||||
{
|
||||
// setup a tmp chain to hande tx hashsets
|
||||
let tmp_chain = setup(".grin.tmp", pow::mine_genesis_block().unwrap());
|
||||
let tmp_chain = setup(tmp_chain_dir, pow::mine_genesis_block().unwrap());
|
||||
tmp_chain.set_txhashset_roots(&mut genesis).unwrap();
|
||||
genesis.header.output_mmr_size = 1;
|
||||
genesis.header.kernel_mmr_size = 1;
|
||||
|
@ -88,6 +93,9 @@ fn mine_genesis_reward_chain() {
|
|||
.unwrap();
|
||||
|
||||
mine_some_on_top(".grin.genesis", genesis, &keychain);
|
||||
// Cleanup chain directories
|
||||
clean_output_dir(tmp_chain_dir);
|
||||
clean_output_dir(".grin.genesis");
|
||||
}
|
||||
|
||||
fn mine_some_on_top<K>(dir: &str, genesis: Block, keychain: &K)
|
||||
|
@ -154,76 +162,84 @@ where
|
|||
#[test]
|
||||
fn mine_forks() {
|
||||
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
||||
let chain = setup(".grin2", pow::mine_genesis_block().unwrap());
|
||||
let kc = ExtKeychain::from_random_seed(false).unwrap();
|
||||
{
|
||||
let chain = setup(".grin2", pow::mine_genesis_block().unwrap());
|
||||
let kc = ExtKeychain::from_random_seed(false).unwrap();
|
||||
|
||||
// add a first block to not fork genesis
|
||||
let prev = chain.head_header().unwrap();
|
||||
let b = prepare_block(&kc, &prev, &chain, 2);
|
||||
chain.process_block(b, chain::Options::SKIP_POW).unwrap();
|
||||
|
||||
// mine and add a few blocks
|
||||
|
||||
for n in 1..4 {
|
||||
// first block for one branch
|
||||
// add a first block to not fork genesis
|
||||
let prev = chain.head_header().unwrap();
|
||||
let b1 = prepare_block(&kc, &prev, &chain, 3 * n);
|
||||
let b = prepare_block(&kc, &prev, &chain, 2);
|
||||
chain.process_block(b, chain::Options::SKIP_POW).unwrap();
|
||||
|
||||
// 2nd block with higher difficulty for other branch
|
||||
let b2 = prepare_block(&kc, &prev, &chain, 3 * n + 1);
|
||||
// mine and add a few blocks
|
||||
|
||||
// process the first block to extend the chain
|
||||
let bhash = b1.hash();
|
||||
chain.process_block(b1, chain::Options::SKIP_POW).unwrap();
|
||||
for n in 1..4 {
|
||||
// first block for one branch
|
||||
let prev = chain.head_header().unwrap();
|
||||
let b1 = prepare_block(&kc, &prev, &chain, 3 * n);
|
||||
|
||||
// checking our new head
|
||||
let head = chain.head().unwrap();
|
||||
assert_eq!(head.height, (n + 1) as u64);
|
||||
assert_eq!(head.last_block_h, bhash);
|
||||
assert_eq!(head.prev_block_h, prev.hash());
|
||||
// 2nd block with higher difficulty for other branch
|
||||
let b2 = prepare_block(&kc, &prev, &chain, 3 * n + 1);
|
||||
|
||||
// process the 2nd block to build a fork with more work
|
||||
let bhash = b2.hash();
|
||||
chain.process_block(b2, chain::Options::SKIP_POW).unwrap();
|
||||
// process the first block to extend the chain
|
||||
let bhash = b1.hash();
|
||||
chain.process_block(b1, chain::Options::SKIP_POW).unwrap();
|
||||
|
||||
// checking head switch
|
||||
let head = chain.head().unwrap();
|
||||
assert_eq!(head.height, (n + 1) as u64);
|
||||
assert_eq!(head.last_block_h, bhash);
|
||||
assert_eq!(head.prev_block_h, prev.hash());
|
||||
// checking our new head
|
||||
let head = chain.head().unwrap();
|
||||
assert_eq!(head.height, (n + 1) as u64);
|
||||
assert_eq!(head.last_block_h, bhash);
|
||||
assert_eq!(head.prev_block_h, prev.hash());
|
||||
|
||||
// process the 2nd block to build a fork with more work
|
||||
let bhash = b2.hash();
|
||||
chain.process_block(b2, chain::Options::SKIP_POW).unwrap();
|
||||
|
||||
// checking head switch
|
||||
let head = chain.head().unwrap();
|
||||
assert_eq!(head.height, (n + 1) as u64);
|
||||
assert_eq!(head.last_block_h, bhash);
|
||||
assert_eq!(head.prev_block_h, prev.hash());
|
||||
}
|
||||
}
|
||||
// Cleanup chain directory
|
||||
clean_output_dir(".grin2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mine_losing_fork() {
|
||||
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
||||
let kc = ExtKeychain::from_random_seed(false).unwrap();
|
||||
let chain = setup(".grin3", pow::mine_genesis_block().unwrap());
|
||||
{
|
||||
let chain = setup(".grin3", pow::mine_genesis_block().unwrap());
|
||||
|
||||
// add a first block we'll be forking from
|
||||
let prev = chain.head_header().unwrap();
|
||||
let b1 = prepare_block(&kc, &prev, &chain, 2);
|
||||
let b1head = b1.header.clone();
|
||||
chain.process_block(b1, chain::Options::SKIP_POW).unwrap();
|
||||
// add a first block we'll be forking from
|
||||
let prev = chain.head_header().unwrap();
|
||||
let b1 = prepare_block(&kc, &prev, &chain, 2);
|
||||
let b1head = b1.header.clone();
|
||||
chain.process_block(b1, chain::Options::SKIP_POW).unwrap();
|
||||
|
||||
// prepare the 2 successor, sibling blocks, one with lower diff
|
||||
let b2 = prepare_block(&kc, &b1head, &chain, 4);
|
||||
let b2head = b2.header.clone();
|
||||
let bfork = prepare_block(&kc, &b1head, &chain, 3);
|
||||
// prepare the 2 successor, sibling blocks, one with lower diff
|
||||
let b2 = prepare_block(&kc, &b1head, &chain, 4);
|
||||
let b2head = b2.header.clone();
|
||||
let bfork = prepare_block(&kc, &b1head, &chain, 3);
|
||||
|
||||
// add higher difficulty first, prepare its successor, then fork
|
||||
// with lower diff
|
||||
chain.process_block(b2, chain::Options::SKIP_POW).unwrap();
|
||||
assert_eq!(chain.head_header().unwrap().hash(), b2head.hash());
|
||||
let b3 = prepare_block(&kc, &b2head, &chain, 5);
|
||||
chain
|
||||
.process_block(bfork, chain::Options::SKIP_POW)
|
||||
.unwrap();
|
||||
// add higher difficulty first, prepare its successor, then fork
|
||||
// with lower diff
|
||||
chain.process_block(b2, chain::Options::SKIP_POW).unwrap();
|
||||
assert_eq!(chain.head_header().unwrap().hash(), b2head.hash());
|
||||
let b3 = prepare_block(&kc, &b2head, &chain, 5);
|
||||
chain
|
||||
.process_block(bfork, chain::Options::SKIP_POW)
|
||||
.unwrap();
|
||||
|
||||
// adding the successor
|
||||
let b3head = b3.header.clone();
|
||||
chain.process_block(b3, chain::Options::SKIP_POW).unwrap();
|
||||
assert_eq!(chain.head_header().unwrap().hash(), b3head.hash());
|
||||
// adding the successor
|
||||
let b3head = b3.header.clone();
|
||||
chain.process_block(b3, chain::Options::SKIP_POW).unwrap();
|
||||
assert_eq!(chain.head_header().unwrap().hash(), b3head.hash());
|
||||
}
|
||||
// Cleanup chain directory
|
||||
clean_output_dir(".grin3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -234,222 +250,234 @@ fn longer_fork() {
|
|||
// prepare 2 chains, the 2nd will be have the forked blocks we can
|
||||
// then send back on the 1st
|
||||
let genesis = pow::mine_genesis_block().unwrap();
|
||||
let chain = setup(".grin4", genesis.clone());
|
||||
{
|
||||
let chain = setup(".grin4", genesis.clone());
|
||||
|
||||
// add blocks to both chains, 20 on the main one, only the first 5
|
||||
// for the forked chain
|
||||
let mut prev = chain.head_header().unwrap();
|
||||
for n in 0..10 {
|
||||
let b = prepare_block(&kc, &prev, &chain, 2 * n + 2);
|
||||
prev = b.header.clone();
|
||||
chain.process_block(b, chain::Options::SKIP_POW).unwrap();
|
||||
// add blocks to both chains, 20 on the main one, only the first 5
|
||||
// for the forked chain
|
||||
let mut prev = chain.head_header().unwrap();
|
||||
for n in 0..10 {
|
||||
let b = prepare_block(&kc, &prev, &chain, 2 * n + 2);
|
||||
prev = b.header.clone();
|
||||
chain.process_block(b, chain::Options::SKIP_POW).unwrap();
|
||||
}
|
||||
|
||||
let forked_block = chain.get_header_by_height(5).unwrap();
|
||||
|
||||
let head = chain.head_header().unwrap();
|
||||
assert_eq!(head.height, 10);
|
||||
assert_eq!(head.hash(), prev.hash());
|
||||
|
||||
let mut prev = forked_block;
|
||||
for n in 0..7 {
|
||||
let b = prepare_fork_block(&kc, &prev, &chain, 2 * n + 11);
|
||||
prev = b.header.clone();
|
||||
chain.process_block(b, chain::Options::SKIP_POW).unwrap();
|
||||
}
|
||||
|
||||
let new_head = prev;
|
||||
|
||||
// After all this the chain should have switched to the fork.
|
||||
let head = chain.head_header().unwrap();
|
||||
assert_eq!(head.height, 12);
|
||||
assert_eq!(head.hash(), new_head.hash());
|
||||
}
|
||||
|
||||
let forked_block = chain.get_header_by_height(5).unwrap();
|
||||
|
||||
let head = chain.head_header().unwrap();
|
||||
assert_eq!(head.height, 10);
|
||||
assert_eq!(head.hash(), prev.hash());
|
||||
|
||||
let mut prev = forked_block;
|
||||
for n in 0..7 {
|
||||
let b = prepare_fork_block(&kc, &prev, &chain, 2 * n + 11);
|
||||
prev = b.header.clone();
|
||||
chain.process_block(b, chain::Options::SKIP_POW).unwrap();
|
||||
}
|
||||
|
||||
let new_head = prev;
|
||||
|
||||
// After all this the chain should have switched to the fork.
|
||||
let head = chain.head_header().unwrap();
|
||||
assert_eq!(head.height, 12);
|
||||
assert_eq!(head.hash(), new_head.hash());
|
||||
// Cleanup chain directory
|
||||
clean_output_dir(".grin4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spend_in_fork_and_compact() {
|
||||
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
||||
util::init_test_logger();
|
||||
let chain = setup(".grin6", pow::mine_genesis_block().unwrap());
|
||||
let prev = chain.head_header().unwrap();
|
||||
let kc = ExtKeychain::from_random_seed(false).unwrap();
|
||||
{
|
||||
let chain = setup(".grin6", pow::mine_genesis_block().unwrap());
|
||||
let prev = chain.head_header().unwrap();
|
||||
let kc = ExtKeychain::from_random_seed(false).unwrap();
|
||||
|
||||
let mut fork_head = prev;
|
||||
let mut fork_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, &fork_head, &chain, 2);
|
||||
let out_id = OutputIdentifier::from_output(&b.outputs()[0]);
|
||||
assert!(out_id.features.is_coinbase());
|
||||
fork_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, &fork_head, &chain, n);
|
||||
// mine the first block and keep track of the block_hash
|
||||
// so we can spend the coinbase later
|
||||
let b = prepare_block(&kc, &fork_head, &chain, 2);
|
||||
let out_id = OutputIdentifier::from_output(&b.outputs()[0]);
|
||||
assert!(out_id.features.is_coinbase());
|
||||
fork_head = b.header.clone();
|
||||
chain.process_block(b, chain::Options::SKIP_POW).unwrap();
|
||||
}
|
||||
chain
|
||||
.process_block(b.clone(), chain::Options::SKIP_POW)
|
||||
.unwrap();
|
||||
|
||||
// Check the height of the "fork block".
|
||||
assert_eq!(fork_head.height, 4);
|
||||
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();
|
||||
// now mine three further blocks
|
||||
for n in 3..6 {
|
||||
let b = prepare_block(&kc, &fork_head, &chain, n);
|
||||
fork_head = b.header.clone();
|
||||
chain.process_block(b, chain::Options::SKIP_POW).unwrap();
|
||||
}
|
||||
|
||||
let tx1 = build::transaction(
|
||||
vec![
|
||||
build::coinbase_input(consensus::REWARD, key_id2.clone()),
|
||||
build::output(consensus::REWARD - 20000, key_id30.clone()),
|
||||
build::with_fee(20000),
|
||||
],
|
||||
&kc,
|
||||
)
|
||||
.unwrap();
|
||||
// Check the height of the "fork block".
|
||||
assert_eq!(fork_head.height, 4);
|
||||
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 next = prepare_block_tx(&kc, &fork_head, &chain, 7, vec![&tx1]);
|
||||
let prev_main = next.header.clone();
|
||||
chain
|
||||
.process_block(next.clone(), chain::Options::SKIP_POW)
|
||||
.unwrap();
|
||||
chain.validate(false).unwrap();
|
||||
|
||||
let tx2 = build::transaction(
|
||||
vec![
|
||||
build::input(consensus::REWARD - 20000, key_id30.clone()),
|
||||
build::output(consensus::REWARD - 40000, key_id31.clone()),
|
||||
build::with_fee(20000),
|
||||
],
|
||||
&kc,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let next = prepare_block_tx(&kc, &prev_main, &chain, 9, vec![&tx2]);
|
||||
let prev_main = next.header.clone();
|
||||
chain.process_block(next, chain::Options::SKIP_POW).unwrap();
|
||||
|
||||
// Full chain validation for completeness.
|
||||
chain.validate(false).unwrap();
|
||||
|
||||
// mine 2 forked blocks from the first
|
||||
let fork = prepare_fork_block_tx(&kc, &fork_head, &chain, 6, vec![&tx1]);
|
||||
let prev_fork = fork.header.clone();
|
||||
chain.process_block(fork, chain::Options::SKIP_POW).unwrap();
|
||||
|
||||
let fork_next = prepare_fork_block_tx(&kc, &prev_fork, &chain, 8, vec![&tx2]);
|
||||
let prev_fork = fork_next.header.clone();
|
||||
chain
|
||||
.process_block(fork_next, chain::Options::SKIP_POW)
|
||||
let tx1 = build::transaction(
|
||||
vec![
|
||||
build::coinbase_input(consensus::REWARD, key_id2.clone()),
|
||||
build::output(consensus::REWARD - 20000, key_id30.clone()),
|
||||
build::with_fee(20000),
|
||||
],
|
||||
&kc,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
chain.validate(false).unwrap();
|
||||
let next = prepare_block_tx(&kc, &fork_head, &chain, 7, vec![&tx1]);
|
||||
let prev_main = next.header.clone();
|
||||
chain
|
||||
.process_block(next.clone(), chain::Options::SKIP_POW)
|
||||
.unwrap();
|
||||
chain.validate(false).unwrap();
|
||||
|
||||
// check state
|
||||
let head = chain.head_header().unwrap();
|
||||
assert_eq!(head.height, 6);
|
||||
assert_eq!(head.hash(), prev_main.hash());
|
||||
assert!(chain
|
||||
.is_unspent(&OutputIdentifier::from_output(&tx2.outputs()[0]))
|
||||
.is_ok());
|
||||
assert!(chain
|
||||
.is_unspent(&OutputIdentifier::from_output(&tx1.outputs()[0]))
|
||||
.is_err());
|
||||
|
||||
// make the fork win
|
||||
let fork_next = prepare_fork_block(&kc, &prev_fork, &chain, 10);
|
||||
let prev_fork = fork_next.header.clone();
|
||||
chain
|
||||
.process_block(fork_next, chain::Options::SKIP_POW)
|
||||
let tx2 = build::transaction(
|
||||
vec![
|
||||
build::input(consensus::REWARD - 20000, key_id30.clone()),
|
||||
build::output(consensus::REWARD - 40000, key_id31.clone()),
|
||||
build::with_fee(20000),
|
||||
],
|
||||
&kc,
|
||||
)
|
||||
.unwrap();
|
||||
chain.validate(false).unwrap();
|
||||
|
||||
// check state
|
||||
let head = chain.head_header().unwrap();
|
||||
assert_eq!(head.height, 7);
|
||||
assert_eq!(head.hash(), prev_fork.hash());
|
||||
assert!(chain
|
||||
.is_unspent(&OutputIdentifier::from_output(&tx2.outputs()[0]))
|
||||
.is_ok());
|
||||
assert!(chain
|
||||
.is_unspent(&OutputIdentifier::from_output(&tx1.outputs()[0]))
|
||||
.is_err());
|
||||
|
||||
// add 20 blocks to go past the test horizon
|
||||
let mut prev = prev_fork;
|
||||
for n in 0..20 {
|
||||
let next = prepare_block(&kc, &prev, &chain, 11 + n);
|
||||
prev = next.header.clone();
|
||||
let next = prepare_block_tx(&kc, &prev_main, &chain, 9, vec![&tx2]);
|
||||
let prev_main = next.header.clone();
|
||||
chain.process_block(next, chain::Options::SKIP_POW).unwrap();
|
||||
}
|
||||
|
||||
chain.validate(false).unwrap();
|
||||
if let Err(e) = chain.compact() {
|
||||
panic!("Error compacting chain: {:?}", e);
|
||||
}
|
||||
if let Err(e) = chain.validate(false) {
|
||||
panic!("Validation error after compacting chain: {:?}", e);
|
||||
// Full chain validation for completeness.
|
||||
chain.validate(false).unwrap();
|
||||
|
||||
// mine 2 forked blocks from the first
|
||||
let fork = prepare_fork_block_tx(&kc, &fork_head, &chain, 6, vec![&tx1]);
|
||||
let prev_fork = fork.header.clone();
|
||||
chain.process_block(fork, chain::Options::SKIP_POW).unwrap();
|
||||
|
||||
let fork_next = prepare_fork_block_tx(&kc, &prev_fork, &chain, 8, vec![&tx2]);
|
||||
let prev_fork = fork_next.header.clone();
|
||||
chain
|
||||
.process_block(fork_next, chain::Options::SKIP_POW)
|
||||
.unwrap();
|
||||
|
||||
chain.validate(false).unwrap();
|
||||
|
||||
// check state
|
||||
let head = chain.head_header().unwrap();
|
||||
assert_eq!(head.height, 6);
|
||||
assert_eq!(head.hash(), prev_main.hash());
|
||||
assert!(chain
|
||||
.is_unspent(&OutputIdentifier::from_output(&tx2.outputs()[0]))
|
||||
.is_ok());
|
||||
assert!(chain
|
||||
.is_unspent(&OutputIdentifier::from_output(&tx1.outputs()[0]))
|
||||
.is_err());
|
||||
|
||||
// make the fork win
|
||||
let fork_next = prepare_fork_block(&kc, &prev_fork, &chain, 10);
|
||||
let prev_fork = fork_next.header.clone();
|
||||
chain
|
||||
.process_block(fork_next, chain::Options::SKIP_POW)
|
||||
.unwrap();
|
||||
chain.validate(false).unwrap();
|
||||
|
||||
// check state
|
||||
let head = chain.head_header().unwrap();
|
||||
assert_eq!(head.height, 7);
|
||||
assert_eq!(head.hash(), prev_fork.hash());
|
||||
assert!(chain
|
||||
.is_unspent(&OutputIdentifier::from_output(&tx2.outputs()[0]))
|
||||
.is_ok());
|
||||
assert!(chain
|
||||
.is_unspent(&OutputIdentifier::from_output(&tx1.outputs()[0]))
|
||||
.is_err());
|
||||
|
||||
// add 20 blocks to go past the test horizon
|
||||
let mut prev = prev_fork;
|
||||
for n in 0..20 {
|
||||
let next = prepare_block(&kc, &prev, &chain, 11 + n);
|
||||
prev = next.header.clone();
|
||||
chain.process_block(next, chain::Options::SKIP_POW).unwrap();
|
||||
}
|
||||
|
||||
chain.validate(false).unwrap();
|
||||
if let Err(e) = chain.compact() {
|
||||
panic!("Error compacting chain: {:?}", e);
|
||||
}
|
||||
if let Err(e) = chain.validate(false) {
|
||||
panic!("Validation error after compacting chain: {:?}", e);
|
||||
}
|
||||
}
|
||||
// Cleanup chain directory
|
||||
clean_output_dir(".grin6");
|
||||
}
|
||||
|
||||
/// Test ability to retrieve block headers for a given output
|
||||
#[test]
|
||||
fn output_header_mappings() {
|
||||
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
||||
let chain = setup(
|
||||
".grin_header_for_output",
|
||||
pow::mine_genesis_block().unwrap(),
|
||||
);
|
||||
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||
let mut reward_outputs = vec![];
|
||||
{
|
||||
let chain = setup(
|
||||
".grin_header_for_output",
|
||||
pow::mine_genesis_block().unwrap(),
|
||||
);
|
||||
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||
let mut reward_outputs = vec![];
|
||||
|
||||
for n in 1..15 {
|
||||
let prev = chain.head_header().unwrap();
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
||||
let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier();
|
||||
let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap();
|
||||
reward_outputs.push(reward.0.clone());
|
||||
let mut b =
|
||||
core::core::Block::new(&prev, vec![], next_header_info.clone().difficulty, reward)
|
||||
for n in 1..15 {
|
||||
let prev = chain.head_header().unwrap();
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
||||
let pk = ExtKeychainPath::new(1, n as u32, 0, 0, 0).to_identifier();
|
||||
let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap();
|
||||
reward_outputs.push(reward.0.clone());
|
||||
let mut b =
|
||||
core::core::Block::new(&prev, vec![], next_header_info.clone().difficulty, reward)
|
||||
.unwrap();
|
||||
b.header.timestamp = prev.timestamp + Duration::seconds(60);
|
||||
b.header.pow.secondary_scaling = next_header_info.secondary_scaling;
|
||||
|
||||
chain.set_txhashset_roots(&mut b).unwrap();
|
||||
|
||||
let edge_bits = if n == 2 {
|
||||
global::min_edge_bits() + 1
|
||||
} else {
|
||||
global::min_edge_bits()
|
||||
};
|
||||
b.header.pow.proof.edge_bits = edge_bits;
|
||||
pow::pow_size(
|
||||
&mut b.header,
|
||||
next_header_info.difficulty,
|
||||
global::proofsize(),
|
||||
edge_bits,
|
||||
)
|
||||
.unwrap();
|
||||
b.header.pow.proof.edge_bits = edge_bits;
|
||||
|
||||
chain.process_block(b, chain::Options::MINE).unwrap();
|
||||
|
||||
let header_for_output = chain
|
||||
.get_header_for_output(&OutputIdentifier::from_output(&reward_outputs[n - 1]))
|
||||
.unwrap();
|
||||
b.header.timestamp = prev.timestamp + Duration::seconds(60);
|
||||
b.header.pow.secondary_scaling = next_header_info.secondary_scaling;
|
||||
assert_eq!(header_for_output.height, n as u64);
|
||||
|
||||
chain.set_txhashset_roots(&mut b).unwrap();
|
||||
chain.validate(false).unwrap();
|
||||
}
|
||||
|
||||
let edge_bits = if n == 2 {
|
||||
global::min_edge_bits() + 1
|
||||
} else {
|
||||
global::min_edge_bits()
|
||||
};
|
||||
b.header.pow.proof.edge_bits = edge_bits;
|
||||
pow::pow_size(
|
||||
&mut b.header,
|
||||
next_header_info.difficulty,
|
||||
global::proofsize(),
|
||||
edge_bits,
|
||||
)
|
||||
.unwrap();
|
||||
b.header.pow.proof.edge_bits = edge_bits;
|
||||
|
||||
chain.process_block(b, chain::Options::MINE).unwrap();
|
||||
|
||||
let header_for_output = chain
|
||||
.get_header_for_output(&OutputIdentifier::from_output(&reward_outputs[n - 1]))
|
||||
.unwrap();
|
||||
assert_eq!(header_for_output.height, n as u64);
|
||||
|
||||
chain.validate(false).unwrap();
|
||||
}
|
||||
|
||||
// Check all output positions are as expected
|
||||
for n in 1..15 {
|
||||
let header_for_output = chain
|
||||
.get_header_for_output(&OutputIdentifier::from_output(&reward_outputs[n - 1]))
|
||||
.unwrap();
|
||||
assert_eq!(header_for_output.height, n as u64);
|
||||
// Check all output positions are as expected
|
||||
for n in 1..15 {
|
||||
let header_for_output = chain
|
||||
.get_header_for_output(&OutputIdentifier::from_output(&reward_outputs[n - 1]))
|
||||
.unwrap();
|
||||
assert_eq!(header_for_output.height, n as u64);
|
||||
}
|
||||
}
|
||||
// Cleanup chain directory
|
||||
clean_output_dir(".grin_header_for_output");
|
||||
}
|
||||
|
||||
fn prepare_block<K>(kc: &K, prev: &BlockHeader, chain: &Chain, diff: u64) -> Block
|
||||
|
|
|
@ -95,4 +95,6 @@ fn test_various_store_indices() {
|
|||
// Check the batch did not commit any changes to the store .
|
||||
assert!(chain_store.get_block(&block_hash).is_ok());
|
||||
}
|
||||
// Cleanup chain directory
|
||||
clean_output_dir(chain_dir);
|
||||
}
|
||||
|
|
|
@ -37,114 +37,37 @@ fn clean_output_dir(dir_name: &str) {
|
|||
#[test]
|
||||
fn test_coinbase_maturity() {
|
||||
let _ = env_logger::init();
|
||||
clean_output_dir(".grin");
|
||||
let chain_dir = ".grin_coinbase";
|
||||
clean_output_dir(chain_dir);
|
||||
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
||||
|
||||
let genesis_block = pow::mine_genesis_block().unwrap();
|
||||
|
||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||
|
||||
let chain = chain::Chain::init(
|
||||
".grin".to_string(),
|
||||
Arc::new(NoopAdapter {}),
|
||||
genesis_block,
|
||||
pow::verify_size,
|
||||
verifier_cache,
|
||||
false,
|
||||
Arc::new(Mutex::new(StopState::new())),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let prev = chain.head_header().unwrap();
|
||||
|
||||
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||
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 key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier();
|
||||
let key_id4 = ExtKeychainPath::new(1, 4, 0, 0, 0).to_identifier();
|
||||
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
||||
let reward = libtx::reward::output(&keychain, &key_id1, 0, false).unwrap();
|
||||
let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), 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();
|
||||
|
||||
pow::pow_size(
|
||||
&mut block.header,
|
||||
next_header_info.difficulty,
|
||||
global::proofsize(),
|
||||
global::min_edge_bits(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(block.outputs().len(), 1);
|
||||
let coinbase_output = block.outputs()[0];
|
||||
assert!(coinbase_output.is_coinbase());
|
||||
|
||||
chain
|
||||
.process_block(block.clone(), chain::Options::MINE)
|
||||
{
|
||||
let chain = chain::Chain::init(
|
||||
".grin".to_string(),
|
||||
Arc::new(NoopAdapter {}),
|
||||
genesis_block,
|
||||
pow::verify_size,
|
||||
verifier_cache,
|
||||
false,
|
||||
Arc::new(Mutex::new(StopState::new())),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let prev = chain.head_header().unwrap();
|
||||
|
||||
let amount = consensus::REWARD;
|
||||
|
||||
let lock_height = 1 + global::coinbase_maturity();
|
||||
assert_eq!(lock_height, 4);
|
||||
|
||||
// here we build a tx that attempts to spend the earlier coinbase output
|
||||
// this is not a valid tx as the coinbase output cannot be spent yet
|
||||
let coinbase_txn = build::transaction(
|
||||
vec![
|
||||
build::coinbase_input(amount, key_id1.clone()),
|
||||
build::output(amount - 2, key_id2.clone()),
|
||||
build::with_fee(2),
|
||||
],
|
||||
&keychain,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let txs = vec![coinbase_txn.clone()];
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward = libtx::reward::output(&keychain, &key_id3, fees, false).unwrap();
|
||||
let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), reward).unwrap();
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().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();
|
||||
|
||||
// Confirm the tx attempting to spend the coinbase output
|
||||
// is not valid at the current block height given the current chain state.
|
||||
match chain.verify_coinbase_maturity(&coinbase_txn) {
|
||||
Ok(_) => {}
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::ImmatureCoinbase => {}
|
||||
_ => panic!("Expected transaction error with immature coinbase."),
|
||||
},
|
||||
}
|
||||
|
||||
pow::pow_size(
|
||||
&mut block.header,
|
||||
next_header_info.difficulty,
|
||||
global::proofsize(),
|
||||
global::min_edge_bits(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// mine enough blocks to increase the height sufficiently for
|
||||
// coinbase to reach maturity and be spendable in the next block
|
||||
for _ in 0..3 {
|
||||
let prev = chain.head_header().unwrap();
|
||||
|
||||
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||
let pk = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
||||
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 key_id3 = ExtKeychainPath::new(1, 3, 0, 0, 0).to_identifier();
|
||||
let key_id4 = ExtKeychainPath::new(1, 4, 0, 0, 0).to_identifier();
|
||||
|
||||
let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap();
|
||||
let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), reward).unwrap();
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
||||
let reward = libtx::reward::output(&keychain, &key_id1, 0, false).unwrap();
|
||||
let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), reward).unwrap();
|
||||
block.header.timestamp = prev.timestamp + Duration::seconds(60);
|
||||
block.header.pow.secondary_scaling = next_header_info.secondary_scaling;
|
||||
|
||||
|
@ -158,37 +81,201 @@ fn test_coinbase_maturity() {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
chain.process_block(block, chain::Options::MINE).unwrap();
|
||||
assert_eq!(block.outputs().len(), 1);
|
||||
let coinbase_output = block.outputs()[0];
|
||||
assert!(coinbase_output.is_coinbase());
|
||||
|
||||
chain
|
||||
.process_block(block.clone(), chain::Options::MINE)
|
||||
.unwrap();
|
||||
|
||||
let prev = chain.head_header().unwrap();
|
||||
|
||||
let amount = consensus::REWARD;
|
||||
|
||||
let lock_height = 1 + global::coinbase_maturity();
|
||||
assert_eq!(lock_height, 4);
|
||||
|
||||
// here we build a tx that attempts to spend the earlier coinbase output
|
||||
// this is not a valid tx as the coinbase output cannot be spent yet
|
||||
let coinbase_txn = build::transaction(
|
||||
vec![
|
||||
build::coinbase_input(amount, key_id1.clone()),
|
||||
build::output(amount - 2, key_id2.clone()),
|
||||
build::with_fee(2),
|
||||
],
|
||||
&keychain,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let txs = vec![coinbase_txn.clone()];
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward = libtx::reward::output(&keychain, &key_id3, fees, false).unwrap();
|
||||
let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), reward).unwrap();
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().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();
|
||||
|
||||
// Confirm the tx attempting to spend the coinbase output
|
||||
// is not valid at the current block height given the current chain state.
|
||||
match chain.verify_coinbase_maturity(&coinbase_txn) {
|
||||
Ok(_) => {}
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::ImmatureCoinbase => {}
|
||||
_ => panic!("Expected transaction error with immature coinbase."),
|
||||
},
|
||||
}
|
||||
|
||||
pow::pow_size(
|
||||
&mut block.header,
|
||||
next_header_info.difficulty,
|
||||
global::proofsize(),
|
||||
global::min_edge_bits(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// mine enough blocks to increase the height sufficiently for
|
||||
// coinbase to reach maturity and be spendable in the next block
|
||||
for _ in 0..3 {
|
||||
let prev = chain.head_header().unwrap();
|
||||
|
||||
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||
let key_id1 = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
||||
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
||||
let reward = libtx::reward::output(&keychain, &key_id1, 0, false).unwrap();
|
||||
let mut block = core::core::Block::new(&prev, vec![], Difficulty::min(), 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();
|
||||
|
||||
pow::pow_size(
|
||||
&mut block.header,
|
||||
next_header_info.difficulty,
|
||||
global::proofsize(),
|
||||
global::min_edge_bits(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(block.outputs().len(), 1);
|
||||
let coinbase_output = block.outputs()[0];
|
||||
assert!(coinbase_output.is_coinbase());
|
||||
|
||||
chain
|
||||
.process_block(block.clone(), chain::Options::MINE)
|
||||
.unwrap();
|
||||
|
||||
let prev = chain.head_header().unwrap();
|
||||
|
||||
let amount = consensus::REWARD;
|
||||
|
||||
let lock_height = 1 + global::coinbase_maturity();
|
||||
assert_eq!(lock_height, 4);
|
||||
|
||||
// here we build a tx that attempts to spend the earlier coinbase output
|
||||
// this is not a valid tx as the coinbase output cannot be spent yet
|
||||
let coinbase_txn = build::transaction(
|
||||
vec![
|
||||
build::coinbase_input(amount, key_id1.clone()),
|
||||
build::output(amount - 2, key_id2.clone()),
|
||||
build::with_fee(2),
|
||||
],
|
||||
&keychain,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let txs = vec![coinbase_txn.clone()];
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward = libtx::reward::output(&keychain, &key_id3, fees, false).unwrap();
|
||||
let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), reward).unwrap();
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().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();
|
||||
|
||||
// Confirm the tx attempting to spend the coinbase output
|
||||
// is not valid at the current block height given the current chain state.
|
||||
match chain.verify_coinbase_maturity(&coinbase_txn) {
|
||||
Ok(_) => {}
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::ImmatureCoinbase => {}
|
||||
_ => panic!("Expected transaction error with immature coinbase."),
|
||||
},
|
||||
}
|
||||
|
||||
pow::pow_size(
|
||||
&mut block.header,
|
||||
next_header_info.difficulty,
|
||||
global::proofsize(),
|
||||
global::min_edge_bits(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// mine enough blocks to increase the height sufficiently for
|
||||
// coinbase to reach maturity and be spendable in the next block
|
||||
for _ in 0..3 {
|
||||
let prev = chain.head_header().unwrap();
|
||||
|
||||
let keychain = ExtKeychain::from_random_seed(false).unwrap();
|
||||
let pk = ExtKeychainPath::new(1, 1, 0, 0, 0).to_identifier();
|
||||
|
||||
let reward = libtx::reward::output(&keychain, &pk, 0, false).unwrap();
|
||||
let mut block =
|
||||
core::core::Block::new(&prev, vec![], Difficulty::min(), reward).unwrap();
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().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();
|
||||
|
||||
pow::pow_size(
|
||||
&mut block.header,
|
||||
next_header_info.difficulty,
|
||||
global::proofsize(),
|
||||
global::min_edge_bits(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
chain.process_block(block, chain::Options::MINE).unwrap();
|
||||
}
|
||||
|
||||
let prev = chain.head_header().unwrap();
|
||||
|
||||
// Confirm the tx spending the coinbase output is now valid.
|
||||
// The coinbase output has matured sufficiently based on current chain state.
|
||||
chain.verify_coinbase_maturity(&coinbase_txn).unwrap();
|
||||
|
||||
let txs = vec![coinbase_txn];
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
||||
let reward = libtx::reward::output(&keychain, &key_id4, fees, false).unwrap();
|
||||
let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), 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();
|
||||
|
||||
pow::pow_size(
|
||||
&mut block.header,
|
||||
next_header_info.difficulty,
|
||||
global::proofsize(),
|
||||
global::min_edge_bits(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = chain.process_block(block, chain::Options::MINE);
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(_) => panic!("we did not expect an error here"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let prev = chain.head_header().unwrap();
|
||||
|
||||
// Confirm the tx spending the coinbase output is now valid.
|
||||
// The coinbase output has matured sufficiently based on current chain state.
|
||||
chain.verify_coinbase_maturity(&coinbase_txn).unwrap();
|
||||
|
||||
let txs = vec![coinbase_txn];
|
||||
let fees = txs.iter().map(|tx| tx.fee()).sum();
|
||||
let next_header_info = consensus::next_difficulty(1, chain.difficulty_iter().unwrap());
|
||||
let reward = libtx::reward::output(&keychain, &key_id4, fees, false).unwrap();
|
||||
let mut block = core::core::Block::new(&prev, txs, Difficulty::min(), 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();
|
||||
|
||||
pow::pow_size(
|
||||
&mut block.header,
|
||||
next_header_info.difficulty,
|
||||
global::proofsize(),
|
||||
global::min_edge_bits(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = chain.process_block(block, chain::Options::MINE);
|
||||
match result {
|
||||
Ok(_) => (),
|
||||
Err(_) => panic!("we did not expect an error here"),
|
||||
};
|
||||
// Cleanup chain directory
|
||||
clean_output_dir(chain_dir);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ use crate::chain::store::ChainStore;
|
|||
use crate::chain::txhashset;
|
||||
use crate::core::core::BlockHeader;
|
||||
use crate::util::file;
|
||||
use grin_core::core::hash::Hashed;
|
||||
|
||||
fn clean_output_dir(dir_name: &str) {
|
||||
let _ = fs::remove_dir_all(dir_name);
|
||||
|
@ -41,54 +40,50 @@ fn test_unexpected_zip() {
|
|||
|
||||
let db_root = format!(".grin_txhashset_zip");
|
||||
clean_output_dir(&db_root);
|
||||
let chain_store = ChainStore::new(&db_root).unwrap();
|
||||
let store = Arc::new(chain_store);
|
||||
txhashset::TxHashSet::open(db_root.clone(), store.clone(), None).unwrap();
|
||||
let head = BlockHeader::default();
|
||||
// First check if everything works out of the box
|
||||
assert!(txhashset::zip_read(db_root.clone(), &head, Some(rand)).is_ok());
|
||||
let zip_path = Path::new(&db_root).join(format!("txhashset_snapshot_{}.zip", rand));
|
||||
let zip_file = File::open(&zip_path).unwrap();
|
||||
assert!(txhashset::zip_write(
|
||||
PathBuf::from(db_root.clone()),
|
||||
zip_file,
|
||||
&BlockHeader::default()
|
||||
)
|
||||
.is_ok());
|
||||
// Remove temp txhashset dir
|
||||
assert!(fs::remove_dir_all(
|
||||
Path::new(&db_root).join(format!("txhashset_zip_{}", head.hash().to_string()))
|
||||
)
|
||||
.is_err());
|
||||
// Then add strange files in the original txhashset folder
|
||||
write_file(db_root.clone());
|
||||
assert!(txhashset::zip_read(db_root.clone(), &head, Some(rand)).is_ok());
|
||||
// Check that the temp dir dos not contains the strange files
|
||||
let txhashset_zip_path =
|
||||
Path::new(&db_root).join(format!("txhashset_zip_{}", head.hash().to_string()));
|
||||
assert!(txhashset_contains_expected_files(
|
||||
format!("txhashset_zip_{}", head.hash().to_string()),
|
||||
txhashset_zip_path.clone()
|
||||
));
|
||||
assert!(fs::remove_dir_all(
|
||||
Path::new(&db_root).join(format!("txhashset_zip_{}", head.hash().to_string()))
|
||||
)
|
||||
.is_err());
|
||||
{
|
||||
let chain_store = ChainStore::new(&db_root).unwrap();
|
||||
let store = Arc::new(chain_store);
|
||||
txhashset::TxHashSet::open(db_root.clone(), store.clone(), None).unwrap();
|
||||
// First check if everything works out of the box
|
||||
assert!(txhashset::zip_read(db_root.clone(), &BlockHeader::default(), Some(rand)).is_ok());
|
||||
let zip_path = Path::new(&db_root).join(format!("txhashset_snapshot_{}.zip", rand));
|
||||
let zip_file = File::open(&zip_path).unwrap();
|
||||
assert!(txhashset::zip_write(
|
||||
PathBuf::from(db_root.clone()),
|
||||
zip_file,
|
||||
&BlockHeader::default()
|
||||
)
|
||||
.is_ok());
|
||||
// Remove temp txhashset dir
|
||||
fs::remove_dir_all(Path::new(&db_root).join(format!("txhashset_zip_{}", rand))).unwrap();
|
||||
// Then add strange files in the original txhashset folder
|
||||
write_file(db_root.clone());
|
||||
assert!(txhashset::zip_read(db_root.clone(), &BlockHeader::default(), Some(rand)).is_ok());
|
||||
// Check that the temp dir dos not contains the strange files
|
||||
let txhashset_zip_path = Path::new(&db_root).join(format!("txhashset_zip_{}", rand));
|
||||
assert!(txhashset_contains_expected_files(
|
||||
format!("txhashset_zip_{}", rand),
|
||||
txhashset_zip_path.clone()
|
||||
));
|
||||
fs::remove_dir_all(Path::new(&db_root).join(format!("txhashset_zip_{}", rand))).unwrap();
|
||||
|
||||
let zip_file = File::open(zip_path).unwrap();
|
||||
assert!(txhashset::zip_write(
|
||||
PathBuf::from(db_root.clone()),
|
||||
zip_file,
|
||||
&BlockHeader::default()
|
||||
)
|
||||
.is_ok());
|
||||
// Check that the txhashset dir dos not contains the strange files
|
||||
let txhashset_path = Path::new(&db_root).join("txhashset");
|
||||
assert!(txhashset_contains_expected_files(
|
||||
"txhashset".to_string(),
|
||||
txhashset_path.clone()
|
||||
));
|
||||
fs::remove_dir_all(Path::new(&db_root).join("txhashset")).unwrap();
|
||||
let zip_file = File::open(zip_path).unwrap();
|
||||
assert!(txhashset::zip_write(
|
||||
PathBuf::from(db_root.clone()),
|
||||
zip_file,
|
||||
&BlockHeader::default()
|
||||
)
|
||||
.is_ok());
|
||||
// Check that the txhashset dir dos not contains the strange files
|
||||
let txhashset_path = Path::new(&db_root).join("txhashset");
|
||||
assert!(txhashset_contains_expected_files(
|
||||
"txhashset".to_string(),
|
||||
txhashset_path.clone()
|
||||
));
|
||||
fs::remove_dir_all(Path::new(&db_root).join("txhashset")).unwrap();
|
||||
}
|
||||
// Cleanup chain directory
|
||||
clean_output_dir(&db_root);
|
||||
}
|
||||
|
||||
fn write_file(db_root: String) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Grin/MimbleWimble for Bitcoiners
|
||||
|
||||
*Read this in other languages:[Korean](grin4bitcoiners_KR.md)
|
||||
|
||||
## Privacy and Fungibility
|
||||
|
||||
There are 3 main properties of Grin transactions that make them private:
|
||||
|
@ -39,7 +41,7 @@ Bitcoin's 10 minute block time has its initial 50 btc reward cut in half every 4
|
|||
|
||||
Nope, no address. All outputs in Grin are unique and have no common data with any previous output. Instead of relying on a known address to send money, transactions have to be built interactively, with two (or more) wallets exchanging data with one another. This interaction **does not require both parties to be online at the same time**. Practically speaking, there are many ways for two programs to interact privately and securely. This interaction could even take place over email or Signal (or carrier pigeons).
|
||||
|
||||
### If transaction information gets removed, can't I just cheat and create money?
|
||||
### If transaction information gets removed, can I just cheat and create money?
|
||||
|
||||
No, and this is where MimbleWimble and Grin shine. Confidential transactions are a form of [homomorphic encryption](https://en.wikipedia.org/wiki/Homomorphic_encryption). Without revealing any amount, Grin can verify that the sum of all transaction inputs equal the sum of transaction outputs, plus the fee. Going even further, comparing the sum of all money created by mining with the total sum of money that's being held, Grin nodes can check the correctness of the total money supply.
|
||||
|
||||
|
|
58
doc/grin4bitcoiners_KR.md
Normal file
58
doc/grin4bitcoiners_KR.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
# Bitcoiner를 위한 Grin/MimbleWimble
|
||||
|
||||
## 프라이버시와 대체가능성(Fungibility)
|
||||
|
||||
Grin 트랜잭션에는 트랜잭션을 프라이빗하게 만드는 3 가지 주요 속성이 있습니다.
|
||||
|
||||
1. 주소가 없습니다.
|
||||
2. 금액은 없습니다.
|
||||
3. 하나는 다른 트랜잭션을 사용하는 2 개의 트랜잭션을 하나의 블록으로 병합하여 모든 중간 정보를 제거 할 수 있습니다.
|
||||
|
||||
처음두 가지 속성은 모든 트랜잭션을 서로 구별 할 수 없음을 의미합니다. 거래에 직접 참여하지 않는 한 모든 입력과 출력은 임의의 데이터 조각처럼 보입니다 (말하자면 출력값과 입력값 모두 랜덤한 곡선 위의 점입니다).
|
||||
|
||||
또한 블록에 트랜잭션이 없습니다. Grin 블록은 마치 하나의 거대한 트랜잭션처럼 보이고 입력과 출력 사이의 모든 연관성이 사라집니다.
|
||||
|
||||
## 확장성(Scalability)
|
||||
|
||||
이전 섹션에서 설명한 것처럼 MimbleWimble 트랜잭션과 블록 포맷 때문에 출력이 다른 트랜잭션의 입력에 의해 직접 소비(spent) 될 때 트랜잭션을 합칠 수 있습니다. (예를 들어 - 문맥의 부드러움을 위해 첨가함, 역자 주 )앨리스가 밥에게 돈을 주고 밥이 캐럴에게 돈을 주면 밥은 결코 연관되지 않은것처럼 보이고 실제로 밥의 트랜잭션은 블록체인에서 보이지 않습니다.
|
||||
|
||||
더 많은 트랜잭션들을 블록에 밀어 넣으면 대부분의 출력이 다른 입력에 의해 조만간 소비됩니다. 따라서 *모든 소비 출력값을(spent outputs) 안전하게 제거 할 수 있습니다*. 그리고 (bitcoin과 유사한 트랜잭션의 수를 가정 한다면)몇 GB 이하로 전체 블록 체인을 저장하고, 다운로드하고, 완벽하게 검증 할 수 있습니다.
|
||||
|
||||
즉, Grin 블록 체인은 트랜잭션 수가 아닌 사용자 수 (사용되지 않은 출력)에 따라 확장됩니다. 그러나 현재 하나 주의하자면 (kernel 이라고 불리는 약 100 바이트의 데이터) 작은 데이터 조각은 각 트랜잭션마다 기다릴 필요가 있습니다. 그러나 이를 최적화하기 위해 노력하고 있습니다.
|
||||
|
||||
## 스크립팅(Scripting)
|
||||
|
||||
아마도 MimbleWimble은 스크립트(Script)를 지원하지 않는다는 말을 들었을 겁니다. 어떤면에서 이 말은 사실입니다. 그러나 암호화 기법 덕분에 Bitcoin에서 스크립트를 필요로 하는 많은 계약은 Elliptic Curve Cryptography의 속성을 사용하여 Grin으로 작성 할 수 있습니다. 지금까지 아래와 같은 구현을 어떻게 하는지 하는지 알고 있습니다. :
|
||||
|
||||
* Multi-signature transactions.
|
||||
* 아토믹 스왑 (Atomic swap).
|
||||
* Time-locked transactions and outputs.
|
||||
* 라이트닝 네트워크 (Lightning Network)
|
||||
|
||||
## 블록 생성주기와 블록 보상비율
|
||||
|
||||
비트코인(Bitcoin)은 처음에 10분마다 50개의 btc를 제공했고 2100만 비트코인이 유통 될 때까지 4 년마다 블록 보상이 반으로 줄어듭니다. Grin의 블록 보상율은 선형적이며(Linear) 블록 보샹율은 떨어지지 않습니다.( 계속 60GRN/block의 비율을 유지한다는 의미 - 역자 주) Grin의 블록 보상은 현재 60 초마다 블록을 생성하고 블록당 60개의 Grin을 받도록 세팅되어 있습니다. 이는 다음과 같은 두가지 이유로 인해 효과가 있습니다. 1) (코인의) 가치저하가 0에 가까우며 2) 매년 무시할 수 없는 양의 동전이 매년 분실되거나 파괴되기 때문입니다 (코인을 계속 사용한다면 코인의 가치저하는 없으며 비트코인처럼 많은 양의 코인이 분실되거나 없어지기 때문에 상기 2가지 이유를 말한것 같음 - 역자 주).
|
||||
|
||||
## FAQ
|
||||
|
||||
### 잠시만요 뭐라구요? 주소가 없다구요?
|
||||
|
||||
네, 주소가 없습니다. Grin의 모든 출력은 유니크 하고 이전 출력과 공통된 데이터가 없습니다. 알려진 주소를 사용하여 돈을 송금하는 대신 두 개 이상의 지갑(주소)이 서로 데이터를 교환하면서 대화식으로 트랜잭션을 만들어야만 합니다. **이 인터렉션에서는 서로 동시에 온라인 상태일 필요는 없습니다**. 실제로 두 프로그램이 프라이빗 하고 안전하게 인터렉션 할 수 있는 방법은 다양합니다. 이 인터렉션은 이메일이나 시그널 (또는 전보 전달 비둘기)을 통해 일어날 수도 있습니다.
|
||||
|
||||
### 트랜잭션 정보가 제거된다면 사기는 치거나 코인을 만들어 낼수 있지 않나요?
|
||||
|
||||
아니요, MimbleWimble과 Grin의 장점이 돋보이는것이 바로 이런 점 입니다. Confidential transaction은 [동형(homomorphic)암호](https://en.wikipedia.org/wiki/Homomophic_encryption)의 한 형태입니다. 금액을 드러내지 않고 Grin은 모든 거래의 입력값의 합계가 거래의 출력값의 합계 + 수수료를 합한 것과 일치하는지 확인이 가능합니다. 더해서 마이닝으로 만들어진 모든 코인의 합계와 보유하고 있는 총 금액과 비교하여, Grin노드는 코인의 모두 얼마나 공급 되었는지 그 정확성을 확인할 수 있습니다.
|
||||
|
||||
### 만약 트랜잭션 릴레이를 받는다면 컷 쓰루 전에는(cut-through) 누구에게 트랜잭션이 속하는지 알 수 없지 않나요?
|
||||
|
||||
어떤 거래에 의해서 어떤 출력값을 소비하고 있는지 알 수 있지만 여기서 데이터의 흔적이 멈춥니다. 모든 입력과 출력은 임의의 데이터 조각처럼 보이므로 돈이 전송되었는지, 여전히 같은 사람에게 속해 있는지, 어떤 출력값이 실제로 전송했는지, 어떤것이 변경되었는지 등은 알 수 없습니다. Grin 트랜잭션들은 *식별 할 수있는 정보가 없습니다*.
|
||||
|
||||
또한, Grin은 [Dandelion relay](dandelion/dandelion_KR.md)를 활용하여 트랜잭션이 발생한 IP 또는 클라이언트에 대한 추가적인 익명성을 제공하고 트랜잭션을 합칠 수 있습니다.
|
||||
|
||||
### 퀀텀 컴퓨타게돈(compute + armageddon) 에 대해서 궁금해요.
|
||||
|
||||
모든 Grin 출력값에는 해싱된 퀀텀 세이프 데이터가 포함되어 있습니다. 양자 컴퓨팅이 현실화되더라도, 기존 동전을 해킹하지 못하게 하는 추가 검증을 안전하게 도입 할 수 있습니다.
|
||||
|
||||
### 어떻게 이 모든일이 가능한거죠?
|
||||
|
||||
이와 관련해서는 [technical introduction](intro_KR.md)문서를 참조하세요.
|
|
@ -1,5 +1,7 @@
|
|||
# Merkle Structures
|
||||
|
||||
*Read this in other languages:[Korean](merkle_KR.md)
|
||||
|
||||
MimbleWimble is designed for users to verify the state of the system given
|
||||
only pruned data. To achieve this goal, all transaction data is committed
|
||||
to the blockchain by means of Merkle trees which should support efficient
|
||||
|
|
113
doc/merkle_KR.md
Normal file
113
doc/merkle_KR.md
Normal file
|
@ -0,0 +1,113 @@
|
|||
# 머클의 구조
|
||||
|
||||
MimbleWimble은 Pruning 데이터만 있는 시스템의 상태를 사용자가 증명하도록 설계되었습니다. 이러한 목표를 달성하기 위해 모든 트랜잭션 데이터는 pruning 된 경우라도 효율적인 업데이트와 serialization을 지원하는 Merkle 트리를 사용하여 블록 체인에 커밋됩니다.
|
||||
|
||||
또한 거의 모든 거래 데이터 (입력, 출력, Excess 및 Excess proof)는 어떤 방식으로 합산 될 수 있으므로 Merkle sum 트리를 기본 옵션으로 처리하고 여기에서 합계를 처리하는 것이 좋습니다.
|
||||
|
||||
Grin의 디자인 목표는 모든 구조를 구현하기 쉽고 가능한 한 간단하게 만드는 것입니다.
|
||||
MimbleWimble은 많은 새로운 암호화 방식을 내 놓았고 이러한 방식을 가능한 한 쉽게 이해할 수 있도록 만들어야합니다.
|
||||
새로운 암호화 방식의 입증 규칙은 스크립트가 없이도 구체화 하기 쉽고 Grin은 매우 명확한 의미론을 가진 프로그래밍 언어로 작성되기 때문에 단순함은 잘 알려진 컨센서스 룰을 달성하는 것에도 좋습니다.
|
||||
|
||||
## Merkle Trees
|
||||
|
||||
각 블록마다 4가지의 머클 트리가 커밋됩니다.
|
||||
|
||||
### Total Output Set
|
||||
|
||||
각 오브젝트는 uspent output 을 나타내는 commitment 또는 spent를 나타내는 NULL 마커 두 가지 중 하나입니다. Unspent 출력에 대한 sum-tree 입니다 (Spent 된 것은 합계에 아무런 영향을 미치지 않습니다). output 세트는 현재 블록이 적용된 *후에* 체인 의 상태를 반영해야합니다.
|
||||
|
||||
Root 합계는 제네시스 블록 이후 모든 Excess의 합계와 같아야합니다.
|
||||
|
||||
설계 요구 사항은 아래와 같습니다.
|
||||
|
||||
1. 효율적으로 추가 되어야 하고 및 unspent 에서 spent 로 업데이트가 되어야 합니다.
|
||||
2. 특정 출력값이 Spent 임을 효율적으로 증명해야 합니다.
|
||||
3. UTXO root간에 diffs를 효율적으로 저장합니다.
|
||||
4. 수백만 개의 항목이 있거나 누락된 데이터가 있는 경우에도 트리에 효율적으로 저장되어야 합니다.
|
||||
5. 노드가 NULL로 커밋되는 경우에는 unspent 하위 항목이 없고 그 데이터를 결과적으로 영구히 삭제할 수 있게 합니다.
|
||||
6. 부분 아카이브 노드에서 Pruning된 트리의 serializtion 및 효율적인 병합을 지원합니다.
|
||||
|
||||
### Output의 증거
|
||||
|
||||
이 트리는 전체 출력 set을 반영하지만 commitment 대신 range proof를 가집니다. 이 트리는 절대 업데이트 되지 않고, 단지 추가되고, 어떤 것이든 더이상 더하지 않습니다. 출력을 소비 할 때 Tree를 삭제하는 것보다는 tree 에서 rangeproof를 삭제하는 것으로 충분합니다.
|
||||
|
||||
설계 요구 사항은 아래와 같습니다.
|
||||
|
||||
1. 부분 아카이브 노드에서 Pruning 된 트리의 serializtion 과 효율적인 병합을 지원해야 합니다.
|
||||
|
||||
### 입력과 출력
|
||||
|
||||
각 객체는 입력 (이전 트랜잭션 출력에 대한 명확한 레퍼런스) 또는 출력 (commitment, rangeproof) 중 하나입니다. 이 sum-tree는 출력에 대한 commitment이고 입력의 commitment에 대한 원본입니다.
|
||||
|
||||
입력 레퍼런스는 이전 commitment의 해시입니다. 모든 unspent 출력은 유니크 해야한다는 것이 컨센서스의 규착입니다.
|
||||
|
||||
Root 합계는 이 블록의 Excess 합계와 같아야 합니다. 이에 대해 다음 섹션을 참고하세요.
|
||||
|
||||
일반적으로 밸리데이터는 이 Merkle 트리의 100 % 또는 0 %를 확인 할 수 있으므로 모든 디자인과 호환됩니다.
|
||||
설계 요구 사항은 다음과 같습니다 :
|
||||
|
||||
1. Proof of publication을 위해서 증명을 효율적으로 포함해야 합니다.
|
||||
|
||||
### Excesses
|
||||
|
||||
각 객체는 (초과, 서명) 형식입니다. 이러한 객체는 Excess를 합친 sum-tree 입니다.
|
||||
|
||||
일반적으로 밸리데이터는 항상 이 트리의 100 %를 확인 할 것이므로 Merkle 구조일 필요가 전혀 없습니다. 그러나 나중에 부분 아카이브 노드를 지원하기 위해 효율적인 Pruning을 지원하기를 원합니다.
|
||||
|
||||
설계 요구 사항 은 아래와 같습니다. :
|
||||
|
||||
1. 부분 아카이브 노드에서 pruning 된 tree의 serialzatoin 과 효율적인 병합을 지원해야 합니다.
|
||||
|
||||
## 제안된 Merkle 구조
|
||||
|
||||
**모든 tree에 대해 다음과 같은 설계가 제안됩니다. Sum-MMR은 모든 노드가 합계에 포함될 데이터 와 자식수의 합계입니다.
|
||||
결과적으로 모든 노드가 모든 하위 노드의 수로 커밋됩니다.**
|
||||
|
||||
[MMRs, or Merkle Mountain Ranges](https://github.com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.md)
|
||||
|
||||
출력값 세트를 위해서 6개의 디자인 원칙은 다음과 같습니다.
|
||||
|
||||
### 효율적인 insert/updates
|
||||
|
||||
즉시적이여야 합니다. (지금은 proof-of-inclusion입니다.). 이 원칙은 균형 잡힌 Merkle tree 디자인에 합당합니다.
|
||||
|
||||
### 효율적인 proof-of-spentness
|
||||
|
||||
Grin은 proof of spentness가 필요하지 않지만 SPV client 을 위해 앞으로 지원하는 것이 좋습니다.
|
||||
|
||||
자식의 수는 tree의 각 개체에 대한 인덱스를 의미합니다. 삽입은 트리의 맨 오른쪽에서만 발생하므로 변경되지 않습니다.
|
||||
|
||||
이렇게하면 동일한 출력이 나중에 트리에 추가 되더라도 영구적으로 proof-of-spentness를 허용하고 동일한 출력에 대해서도 오류 잘못된 증명에 대해서도 방지 할 수 있습니다. 이러한 속성은 삽입 순서가 지정되지 않은 tree에서는 하기 어렵습니다.
|
||||
|
||||
### 효율적인 diffs의 저장
|
||||
|
||||
모든 블록을 저장하면 충분합니다. 업데이트는 실행 취소만큼 수월하고, 블록은 항상 순서대로 처리되기 때문에 트리의 오른쪽에서 인접한 출력 세트를 제거하는 것과 만큼 reorg를 하는 동안 블록을 되감는 것이 간단합니다. 삭제를 지원하도록 설계된 트리의 반복 삭제보다 훨씬 빠릅니다.
|
||||
|
||||
### 데이터가 손실되는 상황에서도 효율적인 tree의 저장
|
||||
|
||||
랜덤한 결과가 소비되었을 때 root 해시를 업데이트하려면 전체 tree를 저장하거나 계산할 필요가 없습니다. 대신 depth 20에 해시 만 저장할 수 있습니다. 쉽세 말하자면 최대 100 만개가 저장됩니다. 그런 다음 각 업데이트는 이 depth보다 위의 해시를 다시 계산하면됩니다 (Bitcoin의 히스토리에는 2 ^ 29 미만의 출력이 있으므로 각 업데이트에 대해 크기가 2 ^ 9 = 512 인 트리를 계산해야 함). 모든 업데이트가 완료되면 root 해시를 다시 계산할 수 있습니다.
|
||||
|
||||
이 깊이는 설정 할 수 있고 출력 set가 증가하거나 사용 가능한 디스크 공간에 따라 변경 될 수 있습니다.
|
||||
|
||||
이런 과정은 어느 Merkle 트리에서 가능하지만 깊이를 어떻게 계산하느냐에 따라 PATRICIA tree 나 다른 prefix tree로 인해 복잡 할 수 있습니다.
|
||||
|
||||
### 사용된 코인 지우기
|
||||
|
||||
코인은 spent 에서 unspent로 이동하지 않으므로 spent 된 코인에 대한 데이터는 더 이상 업데이트나 검색를 위해 필요하지 않습니다.
|
||||
|
||||
### Pruning 된 tree 의 효율적인 Serialization
|
||||
|
||||
모든 노드는 자식 수를 가지므로 밸리데이터는 모든 해시가 없이도 tree 구조를 결정할 수 있으며 형제 노드를 결정할 수 있습니다.
|
||||
|
||||
출력 세트에서 각 노드는 unspent한 자식의 합계도 커밋하므로 밸리데이터는 pruning 된 노드에서 합계가 0인지 여부를 반드시 확인을 하기에 unspent 된 코인의 데이터가 누락되더라도 알게 됩니다.
|
||||
|
||||
## Algorithms
|
||||
|
||||
구현체로서 함께 표시됩니다.
|
||||
( 소스코드를 참고하라는 의미 - 역자 주 )
|
||||
|
||||
## Storage
|
||||
|
||||
합계 tree 데이터 구조를 사용하면 출력 set과 출력 증거를 효율적으로 저장하면서 root 해시 또는 root 합계 (해당되는 경우)를 즉시 검색 할 수 있습니다. 그러나 tree는 시스템에 모든 출력 commitment 와 증거 해시를 포함해야합니다. 이 데이터는 너무 커서 pruning을 고려하더라도 메모리에 영구적으로 저장 될 수 없으며 재시작 할 때마다 처음부터 다시 작성하기에는 비용이 큽니다. (이런 경우에 Bitcoin이 UTXO당 2개의 해시를 가진다고 가정하면 적어도 3.2GB의 용량을 필요로 하는 50M UTXO가 있습니다.). 따라서 이 데이터 구조를 디스크에 저장하는 효율적인 방법이 필요합니다.
|
||||
|
||||
해시 트리의 또 다른 한계는 키 (즉, 출력 commitment)가 주어지면, 그 키와 연관된 tree에서 리프노드를 발견하는 것이 불가능합니다. 그래서 root에서 tree로 찾아 내려 갈 수 없습니다. 따라서 전체 키에 대한 추가 인덱스가 필요합니다. MMR은 append 전용 바이너리 tree이므로 삽입 위치를 기준으로 tree에서 키를 찾을 수 있습니다. 따라서 tree에 삽입 된 키의 전체 인덱스 (즉, 출력 commitment)의 삽입 포지션 또한 필요합니다.
|
|
@ -1,5 +1,7 @@
|
|||
# Merkle Mountain Ranges
|
||||
|
||||
*Read this in other languages:[Korean](mmr_KR.md)
|
||||
|
||||
## Structure
|
||||
|
||||
Merkle Mountain Ranges [1] are an alternative to Merkle trees [2]. While the
|
||||
|
|
117
doc/mmr_KR.md
Normal file
117
doc/mmr_KR.md
Normal file
|
@ -0,0 +1,117 @@
|
|||
# Merkle Mountain Ranges
|
||||
|
||||
## MMR의 구조
|
||||
|
||||
Merkle Mountain Ranges [1]은 Merkle trees [2]의 대안입니다. 후자는 완벽하게 균형 잡힌 이진 트리를 사용하지만 전자는 완벽하게 균형잡힌 binary tree list 거나 오른쪽 상단에서 잘린 single binary tree로 볼 수 있습니다. Merkle Mountain Range (MMR)는 엄격하게 append 에서만 사용됩니다. 원소는 왼쪽에서 오른쪽으로 추가되고, 두 하위 원소가 있는 즉시 부모를 추가하여 그에 따라 범위를 채웁니다.
|
||||
|
||||
다음 그림은 각 노드를 삽입 순서대로 표시한 것입니다. 11 개의 삽입 된 리프와 총크기 19가 있는 range 를 표시합니다.
|
||||
|
||||
```
|
||||
Height
|
||||
|
||||
3 14
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
2 6 13
|
||||
/ \ / \
|
||||
1 2 5 9 12 17
|
||||
/ \ / \ / \ / \ / \
|
||||
0 0 1 3 4 7 8 10 11 15 16 18
|
||||
```
|
||||
|
||||
이 구조는 편평한 리스트로 표시할 수 있습니다. 여기서는 각 노드의 삽입 포지션에서 노드의 높이를 나타냅니다. ( 위의 그림과 아래의 평면 리스트를 비교하면 이해가 쉬움 - 역자 주 )
|
||||
|
||||
```
|
||||
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
||||
0 0 1 0 0 1 2 0 0 1 0 0 1 2 3 0 0 1 0
|
||||
```
|
||||
|
||||
이 구조는 크기(19)에서 간단히 설명 할 수 있습니다. 빠른 binary operation을 사용하기 때문에 MMR 내에서 탐색하는 것도 매우 간단합니다. 주어진 노드의 위치`n`이라면 이 MMR의 높이, 부모의 위치, 형제 등을 계산할 수 있습니다.
|
||||
|
||||
## Hashing 과 Bagging
|
||||
|
||||
Merkle tree와 마찬가지로 MMR의 부모 노드는 자신의 두 하위 노드의 해시 값을 가지고있습니다. Grin은 전체적으로 Blake2b 해시 함수를 사용하고 충돌을 피하기 위해 해싱하기 전에 항상 노드의 위치를 MMR에 추가합니다. 따라서 데이터 `D`를 저장하는 인덱스 `n` 에 리프노드 `l`이 있는 경우엔 (예를 들자면 출력의 경우 해당 데이터는 Pedersen commitment 입니다), 다음과 같이 표시됩니다.
|
||||
|
||||
```
|
||||
Node(l) = Blake2b(n | D)
|
||||
```
|
||||
|
||||
인덱스 `m`에서 어떤 부모 `p`라면 다음과 같습니다.
|
||||
|
||||
```
|
||||
Node(p) = Blake2b(m | Node(left_child(p)) | Node(right_child(p)))
|
||||
```
|
||||
|
||||
Merkle 트리와는 달리 MMR은 일반적으로 하나의 root를 구성하지 않으므로 계산할 방법이 필요합니다. 그렇지 않으면 해시 트리를 사용하는 목적이 무의미 합니다. 이 과정은 [1]에서 설명한 이유 때문에 "bagging the peaks" 라고 합니다.
|
||||
|
||||
먼저 MMR의 최고 높이를 확인합니다. (여기서 확인 할 수 있는 한 가지 방법을 정의 할 것입니다.) 일단 다른 작은 예제 MMR을 보여드리겠습니다. 인덱스는 1부터 시작하고 10 진수가 아닌 2 진수로 작성됩니다.
|
||||
|
||||
```
|
||||
Height
|
||||
|
||||
2 111
|
||||
/ \
|
||||
1 11 110 1010
|
||||
/ \ / \ / \
|
||||
0 1 10 100 101 1000 1001 1011
|
||||
```
|
||||
|
||||
이 MMR에는 11 개의 노드가 있으며 그 피크는 111(7), 1010(10) 및 1011(11) 입니다. 먼저 이진법으로 표현 될 때 가장 왼쪽 첫번째 피크가 항상 가장 (위치가) 높고 항상 "모두 1"이 되는 것에 주목하세요. 그럼 이 피크는 `2^n - 1` 형태의 위치를 가질 것이고 항상 MMR 내부에있는 가장 높은 위치 (그 위치는 전체 크기보다 작습니다)입니다. 사이즈 11의 MMR에 대해서도 반복적으로 처리합니다.
|
||||
|
||||
```
|
||||
2^0 - 1 = 0, and 0 < 11
|
||||
2^1 - 1 = 1, and 1 < 11
|
||||
2^2 - 1 = 3, and 3 < 11
|
||||
2^3 - 1 = 7, and 7 < 11
|
||||
2^4 - 1 = 15, and 15 is not < 11
|
||||
```
|
||||
|
||||
Finally, once all the positions of the peaks are known, "bagging" the peaks
|
||||
consists of hashing them iteratively from the right, using the total size of
|
||||
the MMR as prefix. For a MMR of size N with 3 peaks p1, p2 and p3 we get the
|
||||
final top peak:
|
||||
|
||||
따라서 첫 번째 피크는 7입니다. 다음 피크를 찾으려면 오른쪽 형제에게 "점프" 해야 합니다. 해당 노드가 MMR에 없다면 왼쪽 하위 노드를 가져옵니다. 만약 그 하위노드도 MMR에 없으면, MMR에 있는 노드를 가져올 때까지 왼쪽에 있는 하위 노드를 계속 가져옵니다. 다음 피크를 찾으면 마지막 노드에 도달 할 때까지 프로세스를 반복합니다.
|
||||
|
||||
이 모든 오퍼레이션은 매우 간단합니다. 높이 `h`에 있는 노드의 오른쪽 형제로 점프하는 것은 `2^(h + 1)-1`만큼을 높이 `h`에 추가하는 것입니다. 왼쪽 형제를 가져 가면 `2^h`만큼 값을 뺍니다.
|
||||
|
||||
마지막으로 피크의 모든 위치를 알게되면 피크들을 "bagging"하는 것은 MMR의 전체 크기를 prefix로 사용해서 반복적으로 오른쪽에서부터 해싱하는 것으로 구성됩니다. 3 개의 피크 p1, p2 및 p3을 갖는 크기 N의 MMR에 대해, 다음과 같은 최종 최고 피크를 얻습니다. :
|
||||
|
||||
|
||||
```
|
||||
P = Blake2b(N | Blake2b(N | Node(p3) | Node(p2)) | Node(p1))
|
||||
```
|
||||
|
||||
## Pruning
|
||||
|
||||
Grin에서는 해시되고 MMR에 저장되는 많은 데이터가 결국 제거 될 수 있습니다. 이런 일이 발생하면 해당 MMR안의 일부 리프 해시가 있는지 여부가 불필요하고 리프의 해시를 제거 될 수 있습니다. 충분한 리프가 제거되면 부모의 존재도 불필요하게 될 수 있습니다. 따라서 우리는 그 리프들의 삭제로 인해 MMR의 상당 부분을 pruning 할 수 있습니다.
|
||||
|
||||
MMR의 pruning은 간단한 반복 프로세스에 의존합니다. `X`는 우선 첫번째 제거할 리프로 초기화됩니다.
|
||||
|
||||
1. `X`를 Pruning 한다.
|
||||
2. 만약 `x`가 형제 노드가 있다면 여기서 prunging을 중단한다.
|
||||
3. 만약 `X`가 형제 노드가 없다면 `X`의 부모 노드는 `X`라고 배정된다.
|
||||
|
||||
결과를 시각화하기 위해 첫 번째 MMR 예시에서 시작하여 리프[0, 3, 4, 8, 16]을 제거하면 다음과 같은 pruning MMR이 발생합니다.
|
||||
|
||||
|
||||
```
|
||||
Height
|
||||
|
||||
3 14
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
2 6 13
|
||||
/ / \
|
||||
1 2 9 12 17
|
||||
\ / / \ /
|
||||
0 1 7 10 11 15 18
|
||||
```
|
||||
|
||||
[1] Peter Todd, [merkle-mountain-range](https://github.com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.md)
|
||||
|
||||
[2] [Wikipedia, Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree)
|
|
@ -1,5 +1,7 @@
|
|||
# Grin's Proof-of-Work
|
||||
|
||||
*Read this document in other languages: [Korean](pow_KR.md).*
|
||||
|
||||
This document is meant to outline, at a level suitable for someone without prior knowledge,
|
||||
the algorithms and processes currently involved in Grin's Proof-of-Work system. We'll start
|
||||
with a general overview of cycles in a graph and the Cuckoo Cycle algorithm which forms the
|
||||
|
@ -31,7 +33,7 @@ and some of the motivations behind it.
|
|||
### Cycles in a Graph
|
||||
|
||||
Cuckoo Cycle is an algorithm meant to detect cycles in a bipartite graph of N nodes
|
||||
and M edges. In plainer terms, a bipartite graph is one in which edges (i.e. lines connecting nodes)
|
||||
and M edges. In plain terms, a bipartite graph is one in which edges (i.e. lines connecting nodes)
|
||||
travel only between 2 separate groups of nodes. In the case of the Cuckoo hashtable in Cuckoo Cycle,
|
||||
one side of the graph is an array numbered with odd indices (up to the size of the graph), and the other is numbered with even
|
||||
indices. A node is simply a numbered 'space' on either side of the Cuckoo Table, and an Edge is a
|
||||
|
|
156
doc/pow/pow_KR.md
Normal file
156
doc/pow/pow_KR.md
Normal file
|
@ -0,0 +1,156 @@
|
|||
# Grin의 작업증명
|
||||
|
||||
이 문서는 사전지식이 없는 사람의 수준에서 Grin의 작업증명 시스템과 관련된 알고리즘 및 프로세스를 대략적으로 설명합니다. Grin의 작업 증명의 기초를 형성하는 Cuckoo Cycle 알고리즘과 그래프의 사이클에 대한 개요로 시작하겠습니다. 그런 다음 Cuckoo Cycle과 결합하여 Grin에서 마이닝 전체 형태를 형성하는 시스템인 Grin특유의 세부 정보에 대해서 설명합니다.
|
||||
|
||||
Grin은 현재 활발하게 개발 중이며,이 중 일부 및 전부는 릴리즈 전에 변경 될 수 있습니다.
|
||||
|
||||
## Graphs 와 Cuckoo Cycle
|
||||
|
||||
Grin의 기본 Proof-of-Work 알고리즘은 Cuckoo Cycle 이라고 합니다 이 알고리즘은 Bitcoin 스타일의 하드웨어 경쟁에 (ASIC을 뜻함 - 역자 주) 내성을 갖도록 특별히 설계되었습니다 . Cuckoo cycle은 이론 상으로 slution time 이 CPU 프로세서 또는 GPU 속도가 아닌 메모리 대역폭에 의해 제한된다는 메모리 바운드( [memory bound function](https://en.wikipedia.org/wiki/Memory_bound_function)) 알고리즘 입니다. 따라서 마이닝 Cuckoo cycle solution은 대부분의 상용 하드웨어에서 실행 가능해야만 하고 다른 대부분의 GPU, CPU 또는 ASIC 바인딩 된 작업 증명 알고리즘보다 훨씬 적은 에너지를 필요로 합니다.
|
||||
|
||||
Cuckoo cyle pow의 최신 문서들과 구현은 John Tromp 의 [깃헙](https://github.com/tromp/cuckoo)에서 볼 수 있으며 이 알고리즘의 pow는 그의 작업 결과물입니다. 이 [링크](https://github.com/tromp/cuckoo/blob/master/doc/cuckoo.pdf)는 Cuckoo cycle 의 백서이고 좀 더 기술적인 디테일에 대해서 최고의 자료입니다.
|
||||
|
||||
John Tromp가 Cuckoo Cycle 에 대해 한참을 이야기하는 [Monero Monitor의 마이크가 진하는 팟 캐스트 (Podcast)](https://moneromonitor.com/episodes/2017-09-26-Episode-014.html)도 있습니다. Cuckoo cycle 에 대한 기술적인 세부사항 이라던지 알고리즘 개발의 역사 또는 그 안에 숨겨진 개발 동기등 관련 배경 지식을 더 많이 원하는 사람들을 위해 청취해보기를 추천합니다.
|
||||
|
||||
### Graph 의 Cycle
|
||||
|
||||
Cuckoo Cycle은 N 개의 노드와 M 개의 가장자리로 구성된 양분 그래프의 사이클을 감지하기 위한 알고리즘입니다. 간단히 말해서, 양분 그래프는 엣지(즉, 노드를 연결하는 선)가 2개의 노드 그룹 사이에서만 이동하는 그래프입니다. Cuckoo Cycle에서 Cuckoo 해시 테이블의 경우, 그래프의 한면은 인덱스(그래프 크기까지)가 홀수개 인 배열이고 다른 배열은 짝수 인덱스로 번호가 매겨집니다. 노드는 단순히 Cuckoo Table의 한쪽에 번호가 매겨진 '공간'이고, Edge는 반대쪽에있는 두 노드를 연결하는 선입니다. 아래의 간단한 그래프는 '짝수'측면 (상단)에 4 개의 노드, 홀수 측면 (하단)에 4 개의 노드 및 엣지 (즉, 모든 노드를 연결하는 선)가 없는 그래프를 나타냅니다.
|
||||
|
||||
![alt text](images/cuckoo_base_numbered_minimal.png)
|
||||
|
||||
*제로 엣지가 있는 8개 노드의 그래프*
|
||||
|
||||
랜덤하게 몇개의 엣지들을 그래프에 던져 보겠습니다.
|
||||
|
||||
![alt text](images/cuckoo_base_numbered_few_edges.png)
|
||||
|
||||
*솔루션이 없는 8개의 노드와 4개의 엣지*
|
||||
|
||||
이제 8개의 노드 (N)와 4개의 에지 (M) 또는 N = 8과 M = 4 인 NxM 그래프가 있는 랜덤하게 생성된 그래프가 있습니다. 기본적인 Proof-of-Work는 이 랜덤한 그래프 내에서 특정 길이의 '주기'를 찾거나 단순히 같은 노드에서 시작하고 끝나는 일련의 연결된 노드를 찾는 것과 관련이 있습니다. 따라서 길이 4 (동일한 노드에서 시작하고 끝나는 4 개의 노드를 연결하는 경로)의 사이클을 찾는다면 이 그래프에서 하나도 찾을 수 없습니다.
|
||||
|
||||
노드 수 N과 연관된 엣지의 수인 M를 조정하면 사이클 찾기 문제의 난이도와 현재 그래프에서 사이클이 존재할 확률이 바뀝니다. 예를 들어, POW 문제가 그래프에서 길이 4의 주기를 찾는 것과 관련된다면, 현재의 4/8 난이도 (M / N)는 모든 4 개의 엣지가 0-5-4-1-0 인 완벽한 사이클에서 무작위로 생성 된 것을 의미하고 그것이 정답임을 의미합니다.
|
||||
|
||||
랜덤으로 몇개의 엣지를 다시 더해보겠습니다.
|
||||
|
||||
![alt text](images/cuckoo_base_numbered_more_edges.png)
|
||||
|
||||
*7개의 엣지가 있는 8개의 노드*
|
||||
|
||||
사이클은 아래와 같이 찾을 수 있습니다.
|
||||
|
||||
![alt text](images/cuckoo_base_numbered_more_edges_cycle.png)
|
||||
|
||||
*0-5-4-1-0 에서 발견 할 수 있는 사이클*
|
||||
|
||||
노드 수에 비례하여 엣지의 수를 늘리면 솔루션이 있을 확률이 높아집니다. 위의 그래프에 몇 개의 가장자리가 추가되면 0-5-4-1-0에서 길이 4의 사이클이 나타나고 그래프에는 솔루션이 있습니다.
|
||||
|
||||
따라서, 비율 M / N을 변경하면 무작위로 생성된 엣지를 갖는 그래프에 대한 사이클의 예상 발생 횟수가 변경됩니다.
|
||||
|
||||
위와 같은 작은 그래프의 경우 특정 길이의 주기가 존재하는지 여부를 판별하는 것은 간단합니다. 그러나 그래프가 커질수록 이러한 주기를 감지하는 것이 더욱 어려워집니다. 예를 들어 이 그래프는 길이가 8 인 사이클, 즉 동일한 노드에서 시작하고 끝나는 8 개의 연결된 노드라고 할 수 있습니까?
|
||||
|
||||
![alt text](images/cuckoo_base_numbered_many_edges.png)
|
||||
|
||||
*실제 사이클을 확인해봅시다*
|
||||
|
||||
대답은 독자에게 연습으로 남겨 두지만 전반적인 원리는 다음과 같습니다.
|
||||
|
||||
* 그래프의 크기에 비례해서 그래프에서 사이클을 감지하는 것이 더 어려워집니다.
|
||||
|
||||
* M/N이 커짐에 따라 그래프에서 주어진 길이의 주기 확률이 증가합니다. 즉, 그래프의 노드 수에 따라서 엣지를 더 추가합니다.
|
||||
|
||||
### Cuckoo Cycle
|
||||
|
||||
Cuckoo Cycle 알고리즘은 정확히 이 문제를 해결하기 위해 고안된 특수 알고리즘입니다. Cuckoo Cylcle 알고리즘은 노드를 두 개의 개별 배열로 가능한 위치에 매핑하는 해시에 따라 'Cuckoo Hashtable' 라고 불리는 구조에 값을 삽입하여 수행합니다. 이 문서는 기본 알고리즘에 대해서는 자세히 설명하지 않으며 [백서](https://github.com/tromp/cuckoo/blob/master/doc/cuckoo.pdf)에서 충분히 설명되어 있습니다. 또한 알고리즘에서 속도/메모리 트레이드 오프를 만드는 몇몇 변형이 있지만 이 문서의 (설명)범위를 역시 넘어갑니다.
|
||||
하지만 Grin의 작업증명의 기술적 측면을 계속 설명하기 전에 앞서 알아둬야 할 몇 가지 세부 사항이 있습니다.
|
||||
|
||||
* 위의 그래프에서 '랜덤'에지는 실제로 랜덤하지 않지만 시드배정을 받은 해시함수 인 SIPHASH에 엣지 인덱스(0..N)를 넣어서 생성됩니다. 각 엣지 인덱스는 SIPHASH 함수를 두 번 사용해서 두 개의 엣지 엔드포인를 만들어 냅니다. 첫 번째 엣지 엔드포인트는 2* edge_index이고, 두 번째 엣지 엔드포인트는 2* edge_index+1 입니다. 이 함수의 시드는 블록 헤더의 해시를 기반으로 하고 관 아래에서 자세히 설명합니다.
|
||||
|
||||
* 이 알고리즘에 의해 만들어진 '증명'는 길이가 42 인 사이클을 생성하는 nonce 집합이고 다른 피어들이 쉽게 검증 할 수 있습니다.
|
||||
|
||||
* 위에서 설명한 두 가지 주요 매개 변수는 솔루션의 확률에 영향을 주는 Cuckoo Cycle 알고리즘과 솔루션 검색을 위해 그래프를 검색하는 데 걸리는 시간의 일부가 됩니다.
|
||||
* 위에서 설명한 M/N 비율은 그래프의 크기에 따른 엣지의 숫자를 제어합니다.
|
||||
Cuckoo Cycle이 M을 N의 절반 값으로 고정시킨다면 사이클의 숫자를 최대한 몇몇개로 제한합니다.
|
||||
* 그래프의 사이즈.
|
||||
|
||||
이 파라메터들이 실제로 어떻게 상호작용 하는지는 [이 문서](#mining-loop-difficulty-control-and-timing)를 참고하세요.
|
||||
|
||||
Cuckoo Cycle 알고리즘이 무엇을 하려는지, 파라매터가 솔루션을 찾는데 것이 얼마나 영향을 미치는 지에 대해 기본적으로 이해하고 있으므로, Grin의 POW 시스템의 다른 부분으로 넘어갑니다.
|
||||
|
||||
## Mining in Grin
|
||||
|
||||
위에서 설명한 Cuckoo Cycle은 Grin의 마이닝 프로세스의 윤곽을 설명합니다. 그러나 Grin은 Cuckoo Cycle을 여러 다른 시스템과 함께 사용하여 Proof-of-Work를 만듭니다.
|
||||
|
||||
### 추가적인 난이도 조정에 대해서
|
||||
|
||||
계속 늘어나는 해시파워의 가용성를 가진 네트워크의 필요성 때문에 추가적인 난이도에 대한 제어권을 제공하기 위해 Hashcash 기반 난이도 확인은 다음과 같이 잠재적인 솔루션 세트에 적용됩니다.
|
||||
|
||||
솔루션 논스의 잠재적인 집합인 Blake2b 해시가 (현재는 42 u32의 배열이 사이클 논스를 나타냅니다.) 계속 늘어나는 난이도인 타겟 T보다 작다면, 그 솔루션은 유효하다고 간주됩니다.
|
||||
좀더 정확하게는, 증명 난이도는 현재 해시로 나눈 최대 목표 해시 (2 ^ 256)로 계산되고 정수(integer)로 얻기 위해 반올림됩니다.
|
||||
이 정수가 증가하는 네트워크 난이도보다 큰 경우, POW는 유효한 것으로 간주되며 블록이 유효성 검사를 위해 체인에 제출됩니다.
|
||||
|
||||
즉, 잠재적 증거는 유효한 Cuckoo 사이클을 포함 할뿐만 아니라 또한 목표 난이도보다 높은 값으로 해시해야합니다. 이 난이도는 다음과 같이 유도됩니다.
|
||||
|
||||
### 증가하는 네트워크 난이도
|
||||
|
||||
난이도는 평균 블록 생성 시간을 특정 범위 내로 유지하기 위한(현재는 60 초이지만 변경 될 수 있음)목표로 사용 가능한 네트워크 해시파워에 따라 증가시킬 예정입니다.
|
||||
|
||||
난이도 계산은 Digishield 및 GravityWave 계열의 난이도 계산을 기반으로 ZCash와 매우 비슷합니다. 참조 난이도는 현재 합의 값인 23 개 블록 난이도의 평균입니다.
|
||||
해당 시간 간격은 23개 블록의 시작과 끝의 중간 타임 스탬프 간의 차이를 사용하여 계산됩니다. 시간 범위가 특정 범위보다 높거나 낮으면 (표준 편차를 허용하는 dampening 팩터로 조정) 블록 생성시간을 목표로 하는 값으로 난이도를 높이거나 낮 춥니 다.
|
||||
|
||||
### 마이닝 루프(Loop)
|
||||
|
||||
이러한 시스템은 유요한 작업증명이 체인에 최신 증명을 생성하려 하는 마이닝 루프에 모두 통합됩니다.
|
||||
다음은 메인 마이닝 루프가 단일 반복 중에 수행하는 작업에 대한 개요를 설명합니다.
|
||||
|
||||
* 최신 체인 상태를 얻고 그 위해 블록을 만드는 것은 다음을 포함합니다.
|
||||
* 새값을 가진 블록헤더는 :
|
||||
* [이전 섹션](#증가하는-네트워크-난이도) 에서 설명했던 알고리즘으로 선택된 최신 타겟 난이도
|
||||
* 트랜잭션 풀에서 입증된 트랜잭션의 세트
|
||||
* 코인베이스 트랜잭션
|
||||
* 현재 타임 스탬프
|
||||
* 헤더의 해시에 랜덤하게 생성된 논스를 더해서 추가적으로 랜덤성을 추가
|
||||
* (아직 구현되지 않은) UTXO세트와 fee의 머클루트
|
||||
* 그런 다음 Sub-loop는 현재 2초로 설정된 시간동안 작동하며 다음과 같은 상황이 일어납니다.
|
||||
* 새로운 블록 헤더를 해시해서 새로운 해시 값을 만듭니다.
|
||||
* 다음과 같은 값을 파라메터로 받아들인 Cuckoo 그래프 제너레이터가 초기화 됩니다.
|
||||
* 그래프에서 0..n 인 논스세트의 각 요소 위치 쌍을 만들어내는 SIPHASH 함수의 키로 사용 되는 잠재적인 블록 헤더의 해시
|
||||
* 그래프의 사이즈 ( 합의 값)
|
||||
* 합의 값인 Easiness 값은 M/N비율을 나타내며 이 값은 위에서 나타낸것 처럼 그래프에 나타나는 솔루션의 확률입니다.
|
||||
* Cuckoo 사이클 탐지 알고리즘은 생성된 그래프 내에서 솔루션을 찾으려고 합니다.
|
||||
* 만약 사이클을 찾았다면 증거 Blake2b 해시가 생성되고 현재 타겟의 난이도와 비교됩니다. 이와 관련해서는 위에 [추가적인 난이도 조정에 대해서](#추가적인-난이도-조정에-대해서)에 설명되어 있습니다.
|
||||
* 만약 Blake2b 해시의 난이도가 타겟난이도보다 크거나 같다면 블록은 트랜잭션 풀에 보내지고 유효성을 검사하기 위해 피어들에게 전파됩니다. 그리고 다음 블록을 마이닝을 시작합니다.
|
||||
* 만약 Blake2b 해시의 난이도가 타겟의 난이도 보다 낮다면, 증명된 것 (Blake2b 해시)는 버리고 다시 Sub-loop인 타임 루프가 계속됩니다.
|
||||
* 만약 솔루션을 찾지 못했다면 헤더에 있는 논스값을 1 증가시킵니다. 그리고 헤더의 타임스탬프를 업데이트 합니다. 그래서 다음 루프의 그래프 생성 과정의 기초가 되는 이터레이션 해시가 다른 값이 됩니다.
|
||||
* 솔루션을 찾지 못한채 루프의 타임아웃이 되었다면 제일 위해서부터 다시 시작하고 새로운 트랜잭션을 모으고 새로운 블록을 다 같이 만듭니다.
|
||||
|
||||
### 마이닝 루프 난이도 조정과 타이밍
|
||||
|
||||
마이닝 루프의 난이도를 조정하기 위해서는 위해서 언급한 세 값의 밸런스를 찾아야 합니다.
|
||||
|
||||
* 그래프 크기 (현재 2 ^ n 노드의 크기를 나타내는 비트 시프트 값 n으로 표현, 합의 값은 DEFAULT_SIZESHIFT임).
|
||||
더 작은 그래프는 철저히 더 빨리 검색 될 수 있지만 주어진 Easiness value 에 대해 더 적은 솔루션를 가집니다. 아주 작은 그래프는 더 낮은 Easiness value을 가진 더 큰 그래프와 (비교를 할때) 해답을 찾는 동일한 기회를 가지기 위해 더 높은 Easiness value가 필요합니다.
|
||||
* 'Easiness'합의 값,또는 퍼센티지로 나타나는 그래프의 M / N 비율.
|
||||
이 값이 높을수록 생성 된 그래프에 솔루션이 포함될 확률이 높아집니다. 위와 함께 그래프가 커질수록 주어진 Easiness value 에 대해 더 많은 솔루션이 포함될 가능성이 높아집니다. Cuckoo Cycle 구현은 이 M을 N / 2로 고정시켜 비율을 50%로 만듭니다.
|
||||
* 증가한 네트워크 난이도 해시값.
|
||||
|
||||
마이닝 알고리즘이 Cuckoo 그래프 크기와 증가하는 난이도 사이에서 올바른 균형을 찾을 수 있도록 위의 값들은 신중하게 조정해야 합니다. POW는 대부분 Cuckoo Cycle 기반을 유지할 필요가 있지만 새로운 트랜잭션을 빨리 처리 할 수 있도록 합리적인 수준에서 블록 생성시간을 짧게 해야 합니다.
|
||||
|
||||
예를 들어, 그래프 크기가 너무 작고 easiness (value)가 높다면 주어진 블록에 대해 많은 Cuckoo 사이클 솔루션을 쉽게 찾을 수 있으며 POW는 Cuckoo Cycle이 피하고자 하는 현상 즉, 더 빨리 해싱 할 수 있는 사람들이 선호할 것입니다.
|
||||
그러나 만약에 그래프가 너무 크고 easiness (vaule)가 너무 낮으면 단일 그래프에서 솔루션을 찾는 데 시간이 오래 걸릴 수 있고 새로운 트랜잭션을 수집하는것을 중단 할 수도 있습니다.
|
||||
|
||||
현재 이 값은 그래프 사이즈로 2^12으로 세팅되어 있고 Cuckoo Cycle로 고정된 easiness value는 50% 로 설정되어 있지만 (이러한) 사이즈 값은 테스트를 위한 임시 설정 값입니다. 현재의 마이너 구현체는 최적화되지 않았으며, 그래프 사이즈는 보다 빠르고 최적화 된 Cuckoo Cycle 알고리즘으로 변경 될 필요가 있습니다.
|
||||
|
||||
### Pooling Capability
|
||||
|
||||
Cuckoo Cycle의 poolability에 대한 현재의 우려와는 달리 위에서 설명한 Grin의 POW 구현은 마이닝 풀에 아주 적합합니다.
|
||||
별개의 단일 그래프를 풀기 위한 노력을 증명하는 것은 어려운 반면에, Grin의 작업증명 내에서 조합 된 요소들이 결합하여 모든 마이너들의 공정성뿐만 아니라 'poolability'을 가능하게 만드는 'progress-freeness' 라는 개념을 강제합니다.
|
||||
|
||||
#### Progress Freeness
|
||||
|
||||
Progress-freeness 는 직업 증명의 'poolability'의 핵심이며 POW 문제에 대한 해결책이 합리적인 시간 내에 발견 될 수 있다는 단순한 생각에 기반합니다.
|
||||
예를 들어, 블록체인에서 1분의 POW시간이 있고 (당연히) 마이너가 솔루션을 찾기 위해 평균 1 분을 써야합니다. 이는 POW의 요건을 충족하지만 규모가 큰 마이너에게 이점을 제공합니다.
|
||||
이러한 환경에서 소규모 광부는 적어도 1분을 매번 잃어 버리는 반면 큰 광부는 해결책을 찾으면 (바로 다음루프로) 이동할 수 있습니다.
|
||||
따라서 광업을 상대적으로 progress-free 시키기 위해서는 각 시도마다 상대적으로 적은 시간이 걸리며 다수의 솔루션을 찾는 시도가 있는 POW가 바람직합니다.
|
||||
|
||||
Grin의 Progress - freeness는 Grin의 기본 파라매터가있는 Cuckoo의 솔루션이 일반적으로 대부분의 GPU에서 1초 이내에 발견 될 수 있고 Blake2b 난이도 체크의 추가 요구사항이 그 위에 있다는 사실에 기인합니다 .
|
||||
따라서 Pool의 멤버는 현재의 네트워크 타겟 난이도에 속하는 유효한 Cuckoo 솔루션 (또는 Cuckoo 솔루션의 작은 묶음)을 제출하여 블록의 솔루션에 대해서 작업하고 있음을 증명할 수 있습니다.
|
|
@ -24,6 +24,8 @@ chrono = { version = "0.4.4", features = ["serde"] }
|
|||
grin_core = { path = "../core", version = "1.1.0-beta.1" }
|
||||
grin_store = { path = "../store", version = "1.1.0-beta.1" }
|
||||
grin_util = { path = "../util", version = "1.1.0-beta.1" }
|
||||
grin_chain = { path = "../chain", version = "1.1.0-beta.1" }
|
||||
|
||||
[dev-dependencies]
|
||||
grin_pool = { path = "../pool", version = "1.1.0-beta.1" }
|
||||
|
||||
|
|
|
@ -12,19 +12,16 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::util::RwLock;
|
||||
use std::collections::VecDeque;
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::prelude::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::core::core::hash::Hash;
|
||||
use crate::core::pow::Difficulty;
|
||||
use crate::msg::{read_message, write_message, Hand, Shake, Type, PROTOCOL_VERSION, USER_AGENT};
|
||||
use crate::peer::Peer;
|
||||
use crate::types::{Capabilities, Direction, Error, P2PConfig, PeerAddr, PeerInfo, PeerLiveInfo};
|
||||
use crate::util::RwLock;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::collections::VecDeque;
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Local generated nonce for peer connecting.
|
||||
/// Used for self-connecting detection (on receiver side),
|
||||
|
@ -105,12 +102,7 @@ impl Handshake {
|
|||
user_agent: shake.user_agent,
|
||||
addr: peer_addr,
|
||||
version: shake.version,
|
||||
live_info: Arc::new(RwLock::new(PeerLiveInfo {
|
||||
total_difficulty: shake.total_difficulty,
|
||||
height: 0,
|
||||
last_seen: Utc::now(),
|
||||
stuck_detector: Utc::now(),
|
||||
})),
|
||||
live_info: Arc::new(RwLock::new(PeerLiveInfo::new(shake.total_difficulty))),
|
||||
direction: Direction::Outbound,
|
||||
};
|
||||
|
||||
|
@ -171,12 +163,7 @@ impl Handshake {
|
|||
user_agent: hand.user_agent,
|
||||
addr: resolve_peer_addr(hand.sender_addr, &conn),
|
||||
version: hand.version,
|
||||
live_info: Arc::new(RwLock::new(PeerLiveInfo {
|
||||
total_difficulty: hand.total_difficulty,
|
||||
height: 0,
|
||||
last_seen: Utc::now(),
|
||||
stuck_detector: Utc::now(),
|
||||
})),
|
||||
live_info: Arc::new(RwLock::new(PeerLiveInfo::new(hand.total_difficulty))),
|
||||
direction: Direction::Inbound,
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ extern crate enum_primitive;
|
|||
|
||||
#[macro_use]
|
||||
extern crate grin_core as core;
|
||||
use grin_chain as chain;
|
||||
use grin_util as util;
|
||||
|
||||
#[macro_use]
|
||||
|
|
|
@ -18,6 +18,7 @@ use std::fs::File;
|
|||
use std::net::{Shutdown, TcpStream};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::chain;
|
||||
use crate::conn;
|
||||
use crate::core::core::hash::{Hash, Hashed};
|
||||
use crate::core::pow::Difficulty;
|
||||
|
@ -520,11 +521,11 @@ impl TrackingAdapter {
|
|||
}
|
||||
|
||||
impl ChainAdapter for TrackingAdapter {
|
||||
fn total_difficulty(&self) -> Difficulty {
|
||||
fn total_difficulty(&self) -> Result<Difficulty, chain::Error> {
|
||||
self.adapter.total_difficulty()
|
||||
}
|
||||
|
||||
fn total_height(&self) -> u64 {
|
||||
fn total_height(&self) -> Result<u64, chain::Error> {
|
||||
self.adapter.total_height()
|
||||
}
|
||||
|
||||
|
@ -532,12 +533,16 @@ impl ChainAdapter for TrackingAdapter {
|
|||
self.adapter.get_transaction(kernel_hash)
|
||||
}
|
||||
|
||||
fn tx_kernel_received(&self, kernel_hash: Hash, addr: PeerAddr) {
|
||||
fn tx_kernel_received(&self, kernel_hash: Hash, addr: PeerAddr) -> Result<bool, chain::Error> {
|
||||
self.push_recv(kernel_hash);
|
||||
self.adapter.tx_kernel_received(kernel_hash, addr)
|
||||
}
|
||||
|
||||
fn transaction_received(&self, tx: core::Transaction, stem: bool) {
|
||||
fn transaction_received(
|
||||
&self,
|
||||
tx: core::Transaction,
|
||||
stem: bool,
|
||||
) -> Result<bool, chain::Error> {
|
||||
// Do not track the tx hash for stem txs.
|
||||
// Otherwise we fail to handle the subsequent fluff or embargo expiration
|
||||
// correctly.
|
||||
|
@ -548,27 +553,40 @@ impl ChainAdapter for TrackingAdapter {
|
|||
self.adapter.transaction_received(tx, stem)
|
||||
}
|
||||
|
||||
fn block_received(&self, b: core::Block, addr: PeerAddr, _was_requested: bool) -> bool {
|
||||
fn block_received(
|
||||
&self,
|
||||
b: core::Block,
|
||||
addr: PeerAddr,
|
||||
_was_requested: bool,
|
||||
) -> Result<bool, chain::Error> {
|
||||
let bh = b.hash();
|
||||
self.push_recv(bh);
|
||||
self.adapter.block_received(b, addr, self.has_req(bh))
|
||||
}
|
||||
|
||||
fn compact_block_received(&self, cb: core::CompactBlock, addr: PeerAddr) -> bool {
|
||||
fn compact_block_received(
|
||||
&self,
|
||||
cb: core::CompactBlock,
|
||||
addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
self.push_recv(cb.hash());
|
||||
self.adapter.compact_block_received(cb, addr)
|
||||
}
|
||||
|
||||
fn header_received(&self, bh: core::BlockHeader, addr: PeerAddr) -> bool {
|
||||
fn header_received(&self, bh: core::BlockHeader, addr: PeerAddr) -> Result<bool, chain::Error> {
|
||||
self.push_recv(bh.hash());
|
||||
self.adapter.header_received(bh, addr)
|
||||
}
|
||||
|
||||
fn headers_received(&self, bh: &[core::BlockHeader], addr: PeerAddr) -> bool {
|
||||
fn headers_received(
|
||||
&self,
|
||||
bh: &[core::BlockHeader],
|
||||
addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
self.adapter.headers_received(bh, addr)
|
||||
}
|
||||
|
||||
fn locate_headers(&self, locator: &[Hash]) -> Vec<core::BlockHeader> {
|
||||
fn locate_headers(&self, locator: &[Hash]) -> Result<Vec<core::BlockHeader>, chain::Error> {
|
||||
self.adapter.locate_headers(locator)
|
||||
}
|
||||
|
||||
|
@ -584,7 +602,12 @@ impl ChainAdapter for TrackingAdapter {
|
|||
self.adapter.txhashset_receive_ready()
|
||||
}
|
||||
|
||||
fn txhashset_write(&self, h: Hash, txhashset_data: File, peer_addr: PeerAddr) -> bool {
|
||||
fn txhashset_write(
|
||||
&self,
|
||||
h: Hash,
|
||||
txhashset_data: File,
|
||||
peer_addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
self.adapter.txhashset_write(h, txhashset_data, peer_addr)
|
||||
}
|
||||
|
||||
|
|
116
p2p/src/peers.rs
116
p2p/src/peers.rs
|
@ -19,6 +19,7 @@ use std::sync::Arc;
|
|||
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::chain;
|
||||
use crate::core::core;
|
||||
use crate::core::core::hash::{Hash, Hashed};
|
||||
use crate::core::global;
|
||||
|
@ -136,13 +137,13 @@ impl Peers {
|
|||
|
||||
// Return vec of connected peers that currently advertise more work
|
||||
// (total_difficulty) than we do.
|
||||
pub fn more_work_peers(&self) -> Vec<Arc<Peer>> {
|
||||
pub fn more_work_peers(&self) -> Result<Vec<Arc<Peer>>, chain::Error> {
|
||||
let peers = self.connected_peers();
|
||||
if peers.len() == 0 {
|
||||
return vec![];
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let total_difficulty = self.total_difficulty();
|
||||
let total_difficulty = self.total_difficulty()?;
|
||||
|
||||
let mut max_peers = peers
|
||||
.into_iter()
|
||||
|
@ -150,28 +151,34 @@ impl Peers {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
thread_rng().shuffle(&mut max_peers);
|
||||
max_peers
|
||||
Ok(max_peers)
|
||||
}
|
||||
|
||||
// Return number of connected peers that currently advertise more/same work
|
||||
// (total_difficulty) than/as we do.
|
||||
pub fn more_or_same_work_peers(&self) -> usize {
|
||||
pub fn more_or_same_work_peers(&self) -> Result<usize, chain::Error> {
|
||||
let peers = self.connected_peers();
|
||||
if peers.len() == 0 {
|
||||
return 0;
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let total_difficulty = self.total_difficulty();
|
||||
let total_difficulty = self.total_difficulty()?;
|
||||
|
||||
peers
|
||||
Ok(peers
|
||||
.iter()
|
||||
.filter(|x| x.info.total_difficulty() >= total_difficulty)
|
||||
.count()
|
||||
.count())
|
||||
}
|
||||
|
||||
/// Returns single random peer with more work than us.
|
||||
pub fn more_work_peer(&self) -> Option<Arc<Peer>> {
|
||||
self.more_work_peers().pop()
|
||||
match self.more_work_peers() {
|
||||
Ok(mut peers) => peers.pop(),
|
||||
Err(e) => {
|
||||
error!("failed to get more work peers: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return vec of connected peers that currently have the most worked
|
||||
|
@ -397,10 +404,15 @@ impl Peers {
|
|||
rm.push(peer.info.addr.clone());
|
||||
} else {
|
||||
let (stuck, diff) = peer.is_stuck();
|
||||
if stuck && diff < self.adapter.total_difficulty() {
|
||||
debug!("clean_peers {:?}, stuck peer", peer.info.addr);
|
||||
let _ = self.update_state(peer.info.addr, State::Defunct);
|
||||
rm.push(peer.info.addr.clone());
|
||||
match self.adapter.total_difficulty() {
|
||||
Ok(total_difficulty) => {
|
||||
if stuck && diff < total_difficulty {
|
||||
debug!("clean_peers {:?}, stuck peer", peer.info.addr);
|
||||
let _ = self.update_state(peer.info.addr, State::Defunct);
|
||||
rm.push(peer.info.addr.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => error!("failed to get total difficulty: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -474,11 +486,11 @@ impl Peers {
|
|||
}
|
||||
|
||||
impl ChainAdapter for Peers {
|
||||
fn total_difficulty(&self) -> Difficulty {
|
||||
fn total_difficulty(&self) -> Result<Difficulty, chain::Error> {
|
||||
self.adapter.total_difficulty()
|
||||
}
|
||||
|
||||
fn total_height(&self) -> u64 {
|
||||
fn total_height(&self) -> Result<u64, chain::Error> {
|
||||
self.adapter.total_height()
|
||||
}
|
||||
|
||||
|
@ -486,17 +498,26 @@ impl ChainAdapter for Peers {
|
|||
self.adapter.get_transaction(kernel_hash)
|
||||
}
|
||||
|
||||
fn tx_kernel_received(&self, kernel_hash: Hash, addr: PeerAddr) {
|
||||
fn tx_kernel_received(&self, kernel_hash: Hash, addr: PeerAddr) -> Result<bool, chain::Error> {
|
||||
self.adapter.tx_kernel_received(kernel_hash, addr)
|
||||
}
|
||||
|
||||
fn transaction_received(&self, tx: core::Transaction, stem: bool) {
|
||||
fn transaction_received(
|
||||
&self,
|
||||
tx: core::Transaction,
|
||||
stem: bool,
|
||||
) -> Result<bool, chain::Error> {
|
||||
self.adapter.transaction_received(tx, stem)
|
||||
}
|
||||
|
||||
fn block_received(&self, b: core::Block, peer_addr: PeerAddr, was_requested: bool) -> bool {
|
||||
fn block_received(
|
||||
&self,
|
||||
b: core::Block,
|
||||
peer_addr: PeerAddr,
|
||||
was_requested: bool,
|
||||
) -> Result<bool, chain::Error> {
|
||||
let hash = b.hash();
|
||||
if !self.adapter.block_received(b, peer_addr, was_requested) {
|
||||
if !self.adapter.block_received(b, peer_addr, was_requested)? {
|
||||
// if the peer sent us a block that's intrinsically bad
|
||||
// they are either mistaken or malevolent, both of which require a ban
|
||||
debug!(
|
||||
|
@ -504,15 +525,19 @@ impl ChainAdapter for Peers {
|
|||
hash, peer_addr
|
||||
);
|
||||
self.ban_peer(peer_addr, ReasonForBan::BadBlock);
|
||||
false
|
||||
Ok(false)
|
||||
} else {
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn compact_block_received(&self, cb: core::CompactBlock, peer_addr: PeerAddr) -> bool {
|
||||
fn compact_block_received(
|
||||
&self,
|
||||
cb: core::CompactBlock,
|
||||
peer_addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
let hash = cb.hash();
|
||||
if !self.adapter.compact_block_received(cb, peer_addr) {
|
||||
if !self.adapter.compact_block_received(cb, peer_addr)? {
|
||||
// if the peer sent us a block that's intrinsically bad
|
||||
// they are either mistaken or malevolent, both of which require a ban
|
||||
debug!(
|
||||
|
@ -520,35 +545,43 @@ impl ChainAdapter for Peers {
|
|||
hash, peer_addr
|
||||
);
|
||||
self.ban_peer(peer_addr, ReasonForBan::BadCompactBlock);
|
||||
false
|
||||
Ok(false)
|
||||
} else {
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn header_received(&self, bh: core::BlockHeader, peer_addr: PeerAddr) -> bool {
|
||||
if !self.adapter.header_received(bh, peer_addr) {
|
||||
fn header_received(
|
||||
&self,
|
||||
bh: core::BlockHeader,
|
||||
peer_addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
if !self.adapter.header_received(bh, peer_addr)? {
|
||||
// if the peer sent us a block header that's intrinsically bad
|
||||
// they are either mistaken or malevolent, both of which require a ban
|
||||
self.ban_peer(peer_addr, ReasonForBan::BadBlockHeader);
|
||||
false
|
||||
Ok(false)
|
||||
} else {
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn headers_received(&self, headers: &[core::BlockHeader], peer_addr: PeerAddr) -> bool {
|
||||
if !self.adapter.headers_received(headers, peer_addr) {
|
||||
fn headers_received(
|
||||
&self,
|
||||
headers: &[core::BlockHeader],
|
||||
peer_addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
if !self.adapter.headers_received(headers, peer_addr)? {
|
||||
// if the peer sent us a block header that's intrinsically bad
|
||||
// they are either mistaken or malevolent, both of which require a ban
|
||||
self.ban_peer(peer_addr, ReasonForBan::BadBlockHeader);
|
||||
false
|
||||
Ok(false)
|
||||
} else {
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn locate_headers(&self, hs: &[Hash]) -> Vec<core::BlockHeader> {
|
||||
fn locate_headers(&self, hs: &[Hash]) -> Result<Vec<core::BlockHeader>, chain::Error> {
|
||||
self.adapter.locate_headers(hs)
|
||||
}
|
||||
|
||||
|
@ -564,16 +597,21 @@ impl ChainAdapter for Peers {
|
|||
self.adapter.txhashset_receive_ready()
|
||||
}
|
||||
|
||||
fn txhashset_write(&self, h: Hash, txhashset_data: File, peer_addr: PeerAddr) -> bool {
|
||||
if !self.adapter.txhashset_write(h, txhashset_data, peer_addr) {
|
||||
fn txhashset_write(
|
||||
&self,
|
||||
h: Hash,
|
||||
txhashset_data: File,
|
||||
peer_addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
if !self.adapter.txhashset_write(h, txhashset_data, peer_addr)? {
|
||||
debug!(
|
||||
"Received a bad txhashset data from {}, the peer will be banned",
|
||||
&peer_addr
|
||||
);
|
||||
self.ban_peer(peer_addr, ReasonForBan::BadTxHashSet);
|
||||
false
|
||||
Ok(false)
|
||||
} else {
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,8 +68,8 @@ impl MessageHandler for Protocol {
|
|||
Ok(Some(Response::new(
|
||||
Type::Pong,
|
||||
Pong {
|
||||
total_difficulty: adapter.total_difficulty(),
|
||||
height: adapter.total_height(),
|
||||
total_difficulty: adapter.total_difficulty()?,
|
||||
height: adapter.total_height()?,
|
||||
},
|
||||
writer,
|
||||
)?))
|
||||
|
@ -93,7 +93,7 @@ impl MessageHandler for Protocol {
|
|||
"handle_payload: received tx kernel: {}, msg_len: {}",
|
||||
h, msg.header.msg_len
|
||||
);
|
||||
adapter.tx_kernel_received(h, self.addr);
|
||||
adapter.tx_kernel_received(h, self.addr)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ impl MessageHandler for Protocol {
|
|||
msg.header.msg_len
|
||||
);
|
||||
let tx: core::Transaction = msg.body()?;
|
||||
adapter.transaction_received(tx, false);
|
||||
adapter.transaction_received(tx, false)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ impl MessageHandler for Protocol {
|
|||
msg.header.msg_len
|
||||
);
|
||||
let tx: core::Transaction = msg.body()?;
|
||||
adapter.transaction_received(tx, true);
|
||||
adapter.transaction_received(tx, true)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ impl MessageHandler for Protocol {
|
|||
|
||||
// we can't know at this level whether we requested the block or not,
|
||||
// the boolean should be properly set in higher level adapter
|
||||
adapter.block_received(b, self.addr, false);
|
||||
adapter.block_received(b, self.addr, false)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
@ -176,14 +176,14 @@ impl MessageHandler for Protocol {
|
|||
);
|
||||
let b: core::CompactBlock = msg.body()?;
|
||||
|
||||
adapter.compact_block_received(b, self.addr);
|
||||
adapter.compact_block_received(b, self.addr)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
Type::GetHeaders => {
|
||||
// load headers from the locator
|
||||
let loc: Locator = msg.body()?;
|
||||
let headers = adapter.locate_headers(&loc.hashes);
|
||||
let headers = adapter.locate_headers(&loc.hashes)?;
|
||||
|
||||
// serialize and send all the headers over
|
||||
Ok(Some(Response::new(
|
||||
|
@ -197,7 +197,7 @@ impl MessageHandler for Protocol {
|
|||
// we can go request it from some of our peers
|
||||
Type::Header => {
|
||||
let header: core::BlockHeader = msg.body()?;
|
||||
adapter.header_received(header, self.addr);
|
||||
adapter.header_received(header, self.addr)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
@ -217,7 +217,7 @@ impl MessageHandler for Protocol {
|
|||
headers.push(header);
|
||||
total_bytes_read += bytes_read;
|
||||
}
|
||||
adapter.headers_received(&headers, self.addr);
|
||||
adapter.headers_received(&headers, self.addr)?;
|
||||
}
|
||||
|
||||
// Now check we read the correct total number of bytes off the stream.
|
||||
|
@ -335,7 +335,7 @@ impl MessageHandler for Protocol {
|
|||
let tmp_zip = File::open(tmp)?;
|
||||
let res = self
|
||||
.adapter
|
||||
.txhashset_write(sm_arch.hash, tmp_zip, self.addr);
|
||||
.txhashset_write(sm_arch.hash, tmp_zip, self.addr)?;
|
||||
|
||||
debug!(
|
||||
"handle_payload: txhashset archive for {} at {}, DONE. Data Ok: {}",
|
||||
|
|
|
@ -18,6 +18,7 @@ use std::sync::Arc;
|
|||
use std::time::Duration;
|
||||
use std::{io, thread};
|
||||
|
||||
use crate::chain;
|
||||
use crate::core::core;
|
||||
use crate::core::core::hash::Hash;
|
||||
use crate::core::global;
|
||||
|
@ -137,7 +138,7 @@ impl Server {
|
|||
match TcpStream::connect_timeout(&addr.0, Duration::from_secs(10)) {
|
||||
Ok(mut stream) => {
|
||||
let addr = SocketAddr::new(self.config.host, self.config.port);
|
||||
let total_diff = self.peers.total_difficulty();
|
||||
let total_diff = self.peers.total_difficulty()?;
|
||||
|
||||
let mut peer = Peer::connect(
|
||||
&mut stream,
|
||||
|
@ -166,7 +167,7 @@ impl Server {
|
|||
}
|
||||
|
||||
fn handle_new_peer(&self, mut stream: TcpStream) -> Result<(), Error> {
|
||||
let total_diff = self.peers.total_difficulty();
|
||||
let total_diff = self.peers.total_difficulty()?;
|
||||
|
||||
// accept the peer and add it to the server map
|
||||
let mut peer = Peer::accept(
|
||||
|
@ -229,31 +230,48 @@ impl Server {
|
|||
pub struct DummyAdapter {}
|
||||
|
||||
impl ChainAdapter for DummyAdapter {
|
||||
fn total_difficulty(&self) -> Difficulty {
|
||||
Difficulty::min()
|
||||
fn total_difficulty(&self) -> Result<Difficulty, chain::Error> {
|
||||
Ok(Difficulty::min())
|
||||
}
|
||||
fn total_height(&self) -> u64 {
|
||||
0
|
||||
fn total_height(&self) -> Result<u64, chain::Error> {
|
||||
Ok(0)
|
||||
}
|
||||
fn get_transaction(&self, _h: Hash) -> Option<core::Transaction> {
|
||||
None
|
||||
}
|
||||
fn tx_kernel_received(&self, _h: Hash, _addr: PeerAddr) {}
|
||||
fn transaction_received(&self, _: core::Transaction, _stem: bool) {}
|
||||
fn compact_block_received(&self, _cb: core::CompactBlock, _addr: PeerAddr) -> bool {
|
||||
true
|
||||
|
||||
fn tx_kernel_received(&self, _h: Hash, _addr: PeerAddr) -> Result<bool, chain::Error> {
|
||||
Ok(true)
|
||||
}
|
||||
fn header_received(&self, _bh: core::BlockHeader, _addr: PeerAddr) -> bool {
|
||||
true
|
||||
fn transaction_received(
|
||||
&self,
|
||||
_: core::Transaction,
|
||||
_stem: bool,
|
||||
) -> Result<bool, chain::Error> {
|
||||
Ok(true)
|
||||
}
|
||||
fn block_received(&self, _: core::Block, _: PeerAddr, _: bool) -> bool {
|
||||
true
|
||||
fn compact_block_received(
|
||||
&self,
|
||||
_cb: core::CompactBlock,
|
||||
_addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
Ok(true)
|
||||
}
|
||||
fn headers_received(&self, _: &[core::BlockHeader], _: PeerAddr) -> bool {
|
||||
true
|
||||
fn header_received(
|
||||
&self,
|
||||
_bh: core::BlockHeader,
|
||||
_addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
Ok(true)
|
||||
}
|
||||
fn locate_headers(&self, _: &[Hash]) -> Vec<core::BlockHeader> {
|
||||
vec![]
|
||||
fn block_received(&self, _: core::Block, _: PeerAddr, _: bool) -> Result<bool, chain::Error> {
|
||||
Ok(true)
|
||||
}
|
||||
fn headers_received(&self, _: &[core::BlockHeader], _: PeerAddr) -> Result<bool, chain::Error> {
|
||||
Ok(true)
|
||||
}
|
||||
fn locate_headers(&self, _: &[Hash]) -> Result<Vec<core::BlockHeader>, chain::Error> {
|
||||
Ok(vec![])
|
||||
}
|
||||
fn get_block(&self, _: Hash) -> Option<core::Block> {
|
||||
None
|
||||
|
@ -266,8 +284,13 @@ impl ChainAdapter for DummyAdapter {
|
|||
false
|
||||
}
|
||||
|
||||
fn txhashset_write(&self, _h: Hash, _txhashset_data: File, _peer_addr: PeerAddr) -> bool {
|
||||
false
|
||||
fn txhashset_write(
|
||||
&self,
|
||||
_h: Hash,
|
||||
_txhashset_data: File,
|
||||
_peer_addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn txhashset_download_update(
|
||||
|
|
|
@ -23,6 +23,7 @@ use std::sync::Arc;
|
|||
|
||||
use chrono::prelude::*;
|
||||
|
||||
use crate::chain;
|
||||
use crate::core::core;
|
||||
use crate::core::core::hash::Hash;
|
||||
use crate::core::global;
|
||||
|
@ -63,6 +64,7 @@ pub enum Error {
|
|||
ConnectionClose,
|
||||
Timeout,
|
||||
Store(grin_store::Error),
|
||||
Chain(chain::Error),
|
||||
PeerWithSelf,
|
||||
NoDandelionRelay,
|
||||
ProtocolMismatch {
|
||||
|
@ -88,6 +90,11 @@ impl From<grin_store::Error> for Error {
|
|||
Error::Store(e)
|
||||
}
|
||||
}
|
||||
impl From<chain::Error> for Error {
|
||||
fn from(e: chain::Error) -> Error {
|
||||
Error::Chain(e)
|
||||
}
|
||||
}
|
||||
impl From<io::Error> for Error {
|
||||
fn from(e: io::Error) -> Error {
|
||||
Error::Connection(e)
|
||||
|
@ -358,6 +365,7 @@ pub struct PeerLiveInfo {
|
|||
pub height: u64,
|
||||
pub last_seen: DateTime<Utc>,
|
||||
pub stuck_detector: DateTime<Utc>,
|
||||
pub first_seen: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// General information about a connected peer that's useful to other modules.
|
||||
|
@ -371,6 +379,18 @@ pub struct PeerInfo {
|
|||
pub live_info: Arc<RwLock<PeerLiveInfo>>,
|
||||
}
|
||||
|
||||
impl PeerLiveInfo {
|
||||
pub fn new(difficulty: Difficulty) -> PeerLiveInfo {
|
||||
PeerLiveInfo {
|
||||
total_difficulty: difficulty,
|
||||
height: 0,
|
||||
first_seen: Utc::now(),
|
||||
last_seen: Utc::now(),
|
||||
stuck_detector: Utc::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PeerInfo {
|
||||
/// The current total_difficulty of the peer.
|
||||
pub fn total_difficulty(&self) -> Difficulty {
|
||||
|
@ -391,6 +411,11 @@ impl PeerInfo {
|
|||
self.live_info.read().last_seen
|
||||
}
|
||||
|
||||
/// Time of first_seen for this peer.
|
||||
pub fn first_seen(&self) -> DateTime<Utc> {
|
||||
self.live_info.read().first_seen
|
||||
}
|
||||
|
||||
/// Update the total_difficulty, height and last_seen of the peer.
|
||||
/// Takes a write lock on the live_info.
|
||||
pub fn update(&self, height: u64, total_difficulty: Difficulty) {
|
||||
|
@ -447,37 +472,51 @@ pub struct TxHashSetRead {
|
|||
/// other things.
|
||||
pub trait ChainAdapter: Sync + Send {
|
||||
/// Current total difficulty on our chain
|
||||
fn total_difficulty(&self) -> Difficulty;
|
||||
fn total_difficulty(&self) -> Result<Difficulty, chain::Error>;
|
||||
|
||||
/// Current total height
|
||||
fn total_height(&self) -> u64;
|
||||
fn total_height(&self) -> Result<u64, chain::Error>;
|
||||
|
||||
/// A valid transaction has been received from one of our peers
|
||||
fn transaction_received(&self, tx: core::Transaction, stem: bool);
|
||||
fn transaction_received(&self, tx: core::Transaction, stem: bool)
|
||||
-> Result<bool, chain::Error>;
|
||||
|
||||
fn get_transaction(&self, kernel_hash: Hash) -> Option<core::Transaction>;
|
||||
|
||||
fn tx_kernel_received(&self, kernel_hash: Hash, addr: PeerAddr);
|
||||
fn tx_kernel_received(&self, kernel_hash: Hash, addr: PeerAddr) -> Result<bool, chain::Error>;
|
||||
|
||||
/// A block has been received from one of our peers. Returns true if the
|
||||
/// block could be handled properly and is not deemed defective by the
|
||||
/// chain. Returning false means the block will never be valid and
|
||||
/// may result in the peer being banned.
|
||||
fn block_received(&self, b: core::Block, addr: PeerAddr, was_requested: bool) -> bool;
|
||||
fn block_received(
|
||||
&self,
|
||||
b: core::Block,
|
||||
addr: PeerAddr,
|
||||
was_requested: bool,
|
||||
) -> Result<bool, chain::Error>;
|
||||
|
||||
fn compact_block_received(&self, cb: core::CompactBlock, addr: PeerAddr) -> bool;
|
||||
fn compact_block_received(
|
||||
&self,
|
||||
cb: core::CompactBlock,
|
||||
addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error>;
|
||||
|
||||
fn header_received(&self, bh: core::BlockHeader, addr: PeerAddr) -> bool;
|
||||
fn header_received(&self, bh: core::BlockHeader, addr: PeerAddr) -> Result<bool, chain::Error>;
|
||||
|
||||
/// A set of block header has been received, typically in response to a
|
||||
/// block
|
||||
/// header request.
|
||||
fn headers_received(&self, bh: &[core::BlockHeader], addr: PeerAddr) -> bool;
|
||||
fn headers_received(
|
||||
&self,
|
||||
bh: &[core::BlockHeader],
|
||||
addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error>;
|
||||
|
||||
/// Finds a list of block headers based on the provided locator. Tries to
|
||||
/// identify the common chain and gets the headers that follow it
|
||||
/// immediately.
|
||||
fn locate_headers(&self, locator: &[Hash]) -> Vec<core::BlockHeader>;
|
||||
fn locate_headers(&self, locator: &[Hash]) -> Result<Vec<core::BlockHeader>, chain::Error>;
|
||||
|
||||
/// Gets a full block by its hash.
|
||||
fn get_block(&self, h: Hash) -> Option<core::Block>;
|
||||
|
@ -505,7 +544,12 @@ pub trait ChainAdapter: Sync + Send {
|
|||
/// If we're willing to accept that new state, the data stream will be
|
||||
/// read as a zip file, unzipped and the resulting state files should be
|
||||
/// rewound to the provided indexes.
|
||||
fn txhashset_write(&self, h: Hash, txhashset_data: File, peer_addr: PeerAddr) -> bool;
|
||||
fn txhashset_write(
|
||||
&self,
|
||||
h: Hash,
|
||||
txhashset_data: File,
|
||||
peer_addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error>;
|
||||
}
|
||||
|
||||
/// Additional methods required by the protocol that don't need to be
|
||||
|
|
|
@ -42,7 +42,7 @@ fn peer_handshake() {
|
|||
util::init_test_logger();
|
||||
|
||||
let p2p_config = p2p::P2PConfig {
|
||||
host: "0.0.0.0".parse().unwrap(),
|
||||
host: "127.0.0.1".parse().unwrap(),
|
||||
port: open_port(),
|
||||
peers_allow: None,
|
||||
peers_deny: None,
|
||||
|
|
|
@ -34,87 +34,93 @@ fn test_transaction_pool_block_building() {
|
|||
|
||||
let db_root = ".grin_block_building".to_string();
|
||||
clean_output_dir(db_root.clone());
|
||||
let mut chain = ChainAdapter::init(db_root.clone()).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, &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 block = add_block(header, vec![initial_tx], &mut chain);
|
||||
let header = block.header;
|
||||
|
||||
// Initialize a new pool with our chain adapter.
|
||||
let pool = RwLock::new(test_setup(Arc::new(chain.clone()), verifier_cache));
|
||||
|
||||
let root_tx_1 = test_transaction(&keychain, vec![10, 20], vec![24]);
|
||||
let root_tx_2 = test_transaction(&keychain, vec![30], vec![28]);
|
||||
let root_tx_3 = test_transaction(&keychain, vec![40], vec![38]);
|
||||
|
||||
let child_tx_1 = test_transaction(&keychain, vec![24], vec![22]);
|
||||
let child_tx_2 = test_transaction(&keychain, vec![38], vec![32]);
|
||||
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
let mut chain = ChainAdapter::init(db_root.clone()).unwrap();
|
||||
|
||||
// Add the three root txs to the pool.
|
||||
write_pool
|
||||
.add_to_pool(test_source(), root_tx_1, false, &header)
|
||||
.unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), root_tx_2, false, &header)
|
||||
.unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), root_tx_3, false, &header)
|
||||
.unwrap();
|
||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||
|
||||
// Now add the two child txs to the pool.
|
||||
write_pool
|
||||
.add_to_pool(test_source(), child_tx_1.clone(), false, &header)
|
||||
.unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), child_tx_2.clone(), false, &header)
|
||||
.unwrap();
|
||||
// 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, &key_id, fee, false).unwrap();
|
||||
let mut block = Block::new(&prev_header, txs, Difficulty::min(), reward).unwrap();
|
||||
|
||||
assert_eq!(write_pool.total_size(), 5);
|
||||
}
|
||||
|
||||
let txs = {
|
||||
let read_pool = pool.read();
|
||||
read_pool.prepare_mineable_transactions().unwrap()
|
||||
};
|
||||
// children should have been aggregated into parents
|
||||
assert_eq!(txs.len(), 3);
|
||||
|
||||
let block = add_block(header, txs, &mut chain);
|
||||
|
||||
// Now reconcile the transaction pool with the new block
|
||||
// and check the resulting contents of the pool are what we expect.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
write_pool.reconcile_block(&block).unwrap();
|
||||
|
||||
assert_eq!(write_pool.total_size(), 0);
|
||||
// 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 block = add_block(header, vec![initial_tx], &mut chain);
|
||||
let header = block.header;
|
||||
|
||||
// Initialize a new pool with our chain adapter.
|
||||
let pool = RwLock::new(test_setup(Arc::new(chain.clone()), verifier_cache));
|
||||
|
||||
let root_tx_1 = test_transaction(&keychain, vec![10, 20], vec![24]);
|
||||
let root_tx_2 = test_transaction(&keychain, vec![30], vec![28]);
|
||||
let root_tx_3 = test_transaction(&keychain, vec![40], vec![38]);
|
||||
|
||||
let child_tx_1 = test_transaction(&keychain, vec![24], vec![22]);
|
||||
let child_tx_2 = test_transaction(&keychain, vec![38], vec![32]);
|
||||
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
|
||||
// Add the three root txs to the pool.
|
||||
write_pool
|
||||
.add_to_pool(test_source(), root_tx_1, false, &header)
|
||||
.unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), root_tx_2, false, &header)
|
||||
.unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), root_tx_3, false, &header)
|
||||
.unwrap();
|
||||
|
||||
// Now add the two child txs to the pool.
|
||||
write_pool
|
||||
.add_to_pool(test_source(), child_tx_1.clone(), false, &header)
|
||||
.unwrap();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), child_tx_2.clone(), false, &header)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(write_pool.total_size(), 5);
|
||||
}
|
||||
|
||||
let txs = {
|
||||
let read_pool = pool.read();
|
||||
read_pool.prepare_mineable_transactions().unwrap()
|
||||
};
|
||||
// children should have been aggregated into parents
|
||||
assert_eq!(txs.len(), 3);
|
||||
|
||||
let block = add_block(header, txs, &mut chain);
|
||||
|
||||
// Now reconcile the transaction pool with the new block
|
||||
// and check the resulting contents of the pool are what we expect.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
write_pool.reconcile_block(&block).unwrap();
|
||||
|
||||
assert_eq!(write_pool.total_size(), 0);
|
||||
}
|
||||
}
|
||||
// Cleanup db directory
|
||||
clean_output_dir(db_root.clone());
|
||||
}
|
||||
|
|
|
@ -40,29 +40,30 @@ fn test_block_building_max_weight() {
|
|||
let db_root = ".grin_block_building_max_weight".to_string();
|
||||
clean_output_dir(db_root.clone());
|
||||
|
||||
let mut chain = ChainAdapter::init(db_root.clone()).unwrap();
|
||||
{
|
||||
let mut chain = ChainAdapter::init(db_root.clone()).unwrap();
|
||||
|
||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||
|
||||
// Convenient was to add a new block to the chain.
|
||||
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, &key_id, fee, false).unwrap();
|
||||
let mut block = Block::new(&prev_header, txs, Difficulty::min(), reward).unwrap();
|
||||
// Convenient was to add a new block to the chain.
|
||||
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, &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();
|
||||
// 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
|
||||
};
|
||||
chain.update_db_for_block(&block);
|
||||
block
|
||||
};
|
||||
|
||||
// Initialize the chain/txhashset with an initial block
|
||||
// so we have a non-empty UTXO set.
|
||||
let block = add_block(BlockHeader::default(), vec![], &mut chain);
|
||||
let header = block.header;
|
||||
// Initialize the chain/txhashset with an initial block
|
||||
// so we have a non-empty UTXO set.
|
||||
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.
|
||||
|
@ -72,72 +73,75 @@ fn test_block_building_max_weight() {
|
|||
let block = add_block(header, vec![initial_tx], &mut chain);
|
||||
let header = block.header;
|
||||
|
||||
// Initialize a new pool with our chain adapter.
|
||||
let pool = RwLock::new(test_setup(Arc::new(chain.clone()), verifier_cache));
|
||||
// Initialize a new pool with our chain adapter.
|
||||
let pool = RwLock::new(test_setup(Arc::new(chain.clone()), verifier_cache));
|
||||
|
||||
// Build some dependent txs to add to the txpool.
|
||||
// We will build a block from a subset of these.
|
||||
let txs = vec![
|
||||
test_transaction(&keychain, vec![100], vec![90, 1]),
|
||||
test_transaction(&keychain, vec![90], vec![80, 2]),
|
||||
test_transaction(&keychain, vec![200], vec![199]),
|
||||
test_transaction(&keychain, vec![300], vec![290, 3]),
|
||||
test_transaction(&keychain, vec![290], vec![280, 4]),
|
||||
];
|
||||
// Build some dependent txs to add to the txpool.
|
||||
// We will build a block from a subset of these.
|
||||
let txs = vec![
|
||||
test_transaction(&keychain, vec![100], vec![90, 1]),
|
||||
test_transaction(&keychain, vec![90], vec![80, 2]),
|
||||
test_transaction(&keychain, vec![200], vec![199]),
|
||||
test_transaction(&keychain, vec![300], vec![290, 3]),
|
||||
test_transaction(&keychain, vec![290], vec![280, 4]),
|
||||
];
|
||||
|
||||
// Populate our txpool with the txs.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
for tx in txs {
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx, false, &header)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Check we added them all to the txpool successfully.
|
||||
assert_eq!(pool.read().total_size(), 5);
|
||||
|
||||
// Prepare some "mineable txs" from the txpool.
|
||||
// Note: We cannot fit all the txs from the txpool into a block.
|
||||
let txs = pool.read().prepare_mineable_transactions().unwrap();
|
||||
|
||||
// Check resulting tx aggregation is what we expect.
|
||||
// We expect to produce 2 aggregated txs based on txpool contents.
|
||||
assert_eq!(txs.len(), 2);
|
||||
|
||||
// Check the tx we built is the aggregation of the correct set of underlying txs.
|
||||
// We included 4 out of the 5 txs here.
|
||||
assert_eq!(txs[0].kernels().len(), 1);
|
||||
assert_eq!(txs[1].kernels().len(), 2);
|
||||
|
||||
// Check our weights after aggregation.
|
||||
assert_eq!(txs[0].inputs().len(), 1);
|
||||
assert_eq!(txs[0].outputs().len(), 1);
|
||||
assert_eq!(txs[0].kernels().len(), 1);
|
||||
assert_eq!(txs[0].tx_weight_as_block(), 25);
|
||||
|
||||
assert_eq!(txs[1].inputs().len(), 1);
|
||||
assert_eq!(txs[1].outputs().len(), 3);
|
||||
assert_eq!(txs[1].kernels().len(), 2);
|
||||
assert_eq!(txs[1].tx_weight_as_block(), 70);
|
||||
|
||||
let block = add_block(header, txs, &mut chain);
|
||||
|
||||
// Check contents of the block itself (including coinbase reward).
|
||||
assert_eq!(block.inputs().len(), 2);
|
||||
assert_eq!(block.outputs().len(), 5);
|
||||
assert_eq!(block.kernels().len(), 4);
|
||||
|
||||
// Now reconcile the transaction pool with the new block
|
||||
// and check the resulting contents of the pool are what we expect.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
write_pool.reconcile_block(&block).unwrap();
|
||||
|
||||
// We should still have 2 tx in the pool after accepting the new block.
|
||||
// This one exceeded the max block weight when building the block so
|
||||
// remained in the txpool.
|
||||
assert_eq!(write_pool.total_size(), 2);
|
||||
// Populate our txpool with the txs.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
for tx in txs {
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx, false, &header)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Check we added them all to the txpool successfully.
|
||||
assert_eq!(pool.read().total_size(), 5);
|
||||
|
||||
// Prepare some "mineable txs" from the txpool.
|
||||
// Note: We cannot fit all the txs from the txpool into a block.
|
||||
let txs = pool.read().prepare_mineable_transactions().unwrap();
|
||||
|
||||
// Check resulting tx aggregation is what we expect.
|
||||
// We expect to produce 2 aggregated txs based on txpool contents.
|
||||
assert_eq!(txs.len(), 2);
|
||||
|
||||
// Check the tx we built is the aggregation of the correct set of underlying txs.
|
||||
// We included 4 out of the 5 txs here.
|
||||
assert_eq!(txs[0].kernels().len(), 1);
|
||||
assert_eq!(txs[1].kernels().len(), 2);
|
||||
|
||||
// Check our weights after aggregation.
|
||||
assert_eq!(txs[0].inputs().len(), 1);
|
||||
assert_eq!(txs[0].outputs().len(), 1);
|
||||
assert_eq!(txs[0].kernels().len(), 1);
|
||||
assert_eq!(txs[0].tx_weight_as_block(), 25);
|
||||
|
||||
assert_eq!(txs[1].inputs().len(), 1);
|
||||
assert_eq!(txs[1].outputs().len(), 3);
|
||||
assert_eq!(txs[1].kernels().len(), 2);
|
||||
assert_eq!(txs[1].tx_weight_as_block(), 70);
|
||||
|
||||
let block = add_block(header, txs, &mut chain);
|
||||
|
||||
// Check contents of the block itself (including coinbase reward).
|
||||
assert_eq!(block.inputs().len(), 2);
|
||||
assert_eq!(block.outputs().len(), 5);
|
||||
assert_eq!(block.kernels().len(), 4);
|
||||
|
||||
// Now reconcile the transaction pool with the new block
|
||||
// and check the resulting contents of the pool are what we expect.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
write_pool.reconcile_block(&block).unwrap();
|
||||
|
||||
// We should still have 2 tx in the pool after accepting the new block.
|
||||
// This one exceeded the max block weight when building the block so
|
||||
// remained in the txpool.
|
||||
assert_eq!(write_pool.total_size(), 2);
|
||||
}
|
||||
}
|
||||
// Cleanup db directory
|
||||
clean_output_dir(db_root.clone());
|
||||
}
|
||||
|
|
|
@ -34,153 +34,160 @@ fn test_transaction_pool_block_reconciliation() {
|
|||
|
||||
let db_root = ".grin_block_reconciliation".to_string();
|
||||
clean_output_dir(db_root.clone());
|
||||
let chain = Arc::new(ChainAdapter::init(db_root.clone()).unwrap());
|
||||
|
||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||
|
||||
// Initialize a new pool with our chain adapter.
|
||||
let pool = RwLock::new(test_setup(chain.clone(), verifier_cache.clone()));
|
||||
|
||||
let header = {
|
||||
let height = 1;
|
||||
let key_id = ExtKeychain::derive_key_id(1, height as u32, 0, 0, 0);
|
||||
let reward = libtx::reward::output(&keychain, &key_id, 0, false).unwrap();
|
||||
let genesis = BlockHeader::default();
|
||||
let mut block = Block::new(&genesis, vec![], 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 = genesis.hash();
|
||||
|
||||
chain.update_db_for_block(&block);
|
||||
|
||||
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]);
|
||||
|
||||
let block = {
|
||||
let key_id = ExtKeychain::derive_key_id(1, 2, 0, 0, 0);
|
||||
let fees = initial_tx.fee();
|
||||
let reward = libtx::reward::output(&keychain, &key_id, fees, false).unwrap();
|
||||
let mut block = Block::new(&header, vec![initial_tx], 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 = header.hash();
|
||||
|
||||
chain.update_db_for_block(&block);
|
||||
|
||||
block
|
||||
};
|
||||
|
||||
let header = block.header;
|
||||
|
||||
// Preparation: We will introduce three root pool transactions.
|
||||
// 1. A transaction that should be invalidated because it is exactly
|
||||
// contained in the block.
|
||||
// 2. A transaction that should be invalidated because the input is
|
||||
// consumed in the block, although it is not exactly consumed.
|
||||
// 3. A transaction that should remain after block reconciliation.
|
||||
let block_transaction = test_transaction(&keychain, vec![10], vec![8]);
|
||||
let conflict_transaction = test_transaction(&keychain, vec![20], vec![12, 6]);
|
||||
let valid_transaction = test_transaction(&keychain, vec![30], vec![13, 15]);
|
||||
|
||||
// We will also introduce a few children:
|
||||
// 4. A transaction that descends from transaction 1, that is in
|
||||
// turn exactly contained in the block.
|
||||
let block_child = test_transaction(&keychain, vec![8], vec![5, 1]);
|
||||
// 5. A transaction that descends from transaction 4, that is not
|
||||
// contained in the block at all and should be valid after
|
||||
// reconciliation.
|
||||
let pool_child = test_transaction(&keychain, vec![5], vec![3]);
|
||||
// 6. A transaction that descends from transaction 2 that does not
|
||||
// conflict with anything in the block in any way, but should be
|
||||
// invalidated (orphaned).
|
||||
let conflict_child = test_transaction(&keychain, vec![12], vec![2]);
|
||||
// 7. A transaction that descends from transaction 2 that should be
|
||||
// valid due to its inputs being satisfied by the block.
|
||||
let conflict_valid_child = test_transaction(&keychain, vec![6], vec![4]);
|
||||
// 8. A transaction that descends from transaction 3 that should be
|
||||
// invalidated due to an output conflict.
|
||||
let valid_child_conflict = test_transaction(&keychain, vec![13], vec![9]);
|
||||
// 9. A transaction that descends from transaction 3 that should remain
|
||||
// valid after reconciliation.
|
||||
let valid_child_valid = test_transaction(&keychain, vec![15], vec![11]);
|
||||
// 10. A transaction that descends from both transaction 6 and
|
||||
// transaction 9
|
||||
let mixed_child = test_transaction(&keychain, vec![2, 11], vec![7]);
|
||||
|
||||
let txs_to_add = vec![
|
||||
block_transaction,
|
||||
conflict_transaction,
|
||||
valid_transaction.clone(),
|
||||
block_child,
|
||||
pool_child.clone(),
|
||||
conflict_child,
|
||||
conflict_valid_child.clone(),
|
||||
valid_child_conflict.clone(),
|
||||
valid_child_valid.clone(),
|
||||
mixed_child,
|
||||
];
|
||||
|
||||
// First we add the above transactions to the pool.
|
||||
// All should be accepted.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
assert_eq!(write_pool.total_size(), 0);
|
||||
let chain = Arc::new(ChainAdapter::init(db_root.clone()).unwrap());
|
||||
|
||||
for tx in &txs_to_add {
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx.clone(), false, &header)
|
||||
.unwrap();
|
||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||
|
||||
// Initialize a new pool with our chain adapter.
|
||||
let pool = RwLock::new(test_setup(chain.clone(), verifier_cache.clone()));
|
||||
|
||||
let header = {
|
||||
let height = 1;
|
||||
let key_id = ExtKeychain::derive_key_id(1, height as u32, 0, 0, 0);
|
||||
let reward = libtx::reward::output(&keychain, &key_id, 0, false).unwrap();
|
||||
let genesis = BlockHeader::default();
|
||||
let mut block = Block::new(&genesis, vec![], 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 = genesis.hash();
|
||||
|
||||
chain.update_db_for_block(&block);
|
||||
|
||||
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]);
|
||||
|
||||
let block = {
|
||||
let key_id = ExtKeychain::derive_key_id(1, 2, 0, 0, 0);
|
||||
let fees = initial_tx.fee();
|
||||
let reward = libtx::reward::output(&keychain, &key_id, fees, false).unwrap();
|
||||
let mut block =
|
||||
Block::new(&header, vec![initial_tx], 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 = header.hash();
|
||||
|
||||
chain.update_db_for_block(&block);
|
||||
|
||||
block
|
||||
};
|
||||
|
||||
let header = block.header;
|
||||
|
||||
// Preparation: We will introduce three root pool transactions.
|
||||
// 1. A transaction that should be invalidated because it is exactly
|
||||
// contained in the block.
|
||||
// 2. A transaction that should be invalidated because the input is
|
||||
// consumed in the block, although it is not exactly consumed.
|
||||
// 3. A transaction that should remain after block reconciliation.
|
||||
let block_transaction = test_transaction(&keychain, vec![10], vec![8]);
|
||||
let conflict_transaction = test_transaction(&keychain, vec![20], vec![12, 6]);
|
||||
let valid_transaction = test_transaction(&keychain, vec![30], vec![13, 15]);
|
||||
|
||||
// We will also introduce a few children:
|
||||
// 4. A transaction that descends from transaction 1, that is in
|
||||
// turn exactly contained in the block.
|
||||
let block_child = test_transaction(&keychain, vec![8], vec![5, 1]);
|
||||
// 5. A transaction that descends from transaction 4, that is not
|
||||
// contained in the block at all and should be valid after
|
||||
// reconciliation.
|
||||
let pool_child = test_transaction(&keychain, vec![5], vec![3]);
|
||||
// 6. A transaction that descends from transaction 2 that does not
|
||||
// conflict with anything in the block in any way, but should be
|
||||
// invalidated (orphaned).
|
||||
let conflict_child = test_transaction(&keychain, vec![12], vec![2]);
|
||||
// 7. A transaction that descends from transaction 2 that should be
|
||||
// valid due to its inputs being satisfied by the block.
|
||||
let conflict_valid_child = test_transaction(&keychain, vec![6], vec![4]);
|
||||
// 8. A transaction that descends from transaction 3 that should be
|
||||
// invalidated due to an output conflict.
|
||||
let valid_child_conflict = test_transaction(&keychain, vec![13], vec![9]);
|
||||
// 9. A transaction that descends from transaction 3 that should remain
|
||||
// valid after reconciliation.
|
||||
let valid_child_valid = test_transaction(&keychain, vec![15], vec![11]);
|
||||
// 10. A transaction that descends from both transaction 6 and
|
||||
// transaction 9
|
||||
let mixed_child = test_transaction(&keychain, vec![2, 11], vec![7]);
|
||||
|
||||
let txs_to_add = vec![
|
||||
block_transaction,
|
||||
conflict_transaction,
|
||||
valid_transaction.clone(),
|
||||
block_child,
|
||||
pool_child.clone(),
|
||||
conflict_child,
|
||||
conflict_valid_child.clone(),
|
||||
valid_child_conflict.clone(),
|
||||
valid_child_valid.clone(),
|
||||
mixed_child,
|
||||
];
|
||||
|
||||
// First we add the above transactions to the pool.
|
||||
// All should be accepted.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
assert_eq!(write_pool.total_size(), 0);
|
||||
|
||||
for tx in &txs_to_add {
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx.clone(), false, &header)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(write_pool.total_size(), txs_to_add.len());
|
||||
}
|
||||
|
||||
assert_eq!(write_pool.total_size(), txs_to_add.len());
|
||||
}
|
||||
|
||||
// Now we prepare the block that will cause the above conditions to be met.
|
||||
// First, the transactions we want in the block:
|
||||
// - Copy of 1
|
||||
let block_tx_1 = test_transaction(&keychain, vec![10], vec![8]);
|
||||
// - Conflict w/ 2, satisfies 7
|
||||
let block_tx_2 = test_transaction(&keychain, vec![20], vec![6]);
|
||||
// - Copy of 4
|
||||
let block_tx_3 = test_transaction(&keychain, vec![8], vec![5, 1]);
|
||||
// - Output conflict w/ 8
|
||||
let block_tx_4 = test_transaction(&keychain, vec![40], vec![9, 31]);
|
||||
|
||||
let block_txs = vec![block_tx_1, block_tx_2, block_tx_3, block_tx_4];
|
||||
|
||||
// Now apply this block.
|
||||
let block = {
|
||||
let key_id = ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
||||
let fees = block_txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward = libtx::reward::output(&keychain, &key_id, fees, false).unwrap();
|
||||
let mut block = Block::new(&header, block_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 = header.hash();
|
||||
|
||||
chain.update_db_for_block(&block);
|
||||
block
|
||||
};
|
||||
|
||||
// Check the pool still contains everything we expect at this point.
|
||||
{
|
||||
let write_pool = pool.write();
|
||||
assert_eq!(write_pool.total_size(), txs_to_add.len());
|
||||
}
|
||||
|
||||
// And reconcile the pool with this latest block.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
write_pool.reconcile_block(&block).unwrap();
|
||||
|
||||
assert_eq!(write_pool.total_size(), 4);
|
||||
assert_eq!(write_pool.txpool.entries[0].tx, valid_transaction);
|
||||
assert_eq!(write_pool.txpool.entries[1].tx, pool_child);
|
||||
assert_eq!(write_pool.txpool.entries[2].tx, conflict_valid_child);
|
||||
assert_eq!(write_pool.txpool.entries[3].tx, valid_child_valid);
|
||||
// Now we prepare the block that will cause the above conditions to be met.
|
||||
// First, the transactions we want in the block:
|
||||
// - Copy of 1
|
||||
let block_tx_1 = test_transaction(&keychain, vec![10], vec![8]);
|
||||
// - Conflict w/ 2, satisfies 7
|
||||
let block_tx_2 = test_transaction(&keychain, vec![20], vec![6]);
|
||||
// - Copy of 4
|
||||
let block_tx_3 = test_transaction(&keychain, vec![8], vec![5, 1]);
|
||||
// - Output conflict w/ 8
|
||||
let block_tx_4 = test_transaction(&keychain, vec![40], vec![9, 31]);
|
||||
|
||||
let block_txs = vec![block_tx_1, block_tx_2, block_tx_3, block_tx_4];
|
||||
|
||||
// Now apply this block.
|
||||
let block = {
|
||||
let key_id = ExtKeychain::derive_key_id(1, 3, 0, 0, 0);
|
||||
let fees = block_txs.iter().map(|tx| tx.fee()).sum();
|
||||
let reward = libtx::reward::output(&keychain, &key_id, fees, false).unwrap();
|
||||
let mut block = Block::new(&header, block_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 = header.hash();
|
||||
|
||||
chain.update_db_for_block(&block);
|
||||
block
|
||||
};
|
||||
|
||||
// Check the pool still contains everything we expect at this point.
|
||||
{
|
||||
let write_pool = pool.write();
|
||||
assert_eq!(write_pool.total_size(), txs_to_add.len());
|
||||
}
|
||||
|
||||
// And reconcile the pool with this latest block.
|
||||
{
|
||||
|
||||
let mut write_pool = pool.write();
|
||||
write_pool.reconcile_block(&block).unwrap();
|
||||
|
||||
assert_eq!(write_pool.total_size(), 4);
|
||||
assert_eq!(write_pool.txpool.entries[0].tx, valid_transaction);
|
||||
assert_eq!(write_pool.txpool.entries[1].tx, pool_child);
|
||||
assert_eq!(write_pool.txpool.entries[2].tx, conflict_valid_child);
|
||||
assert_eq!(write_pool.txpool.entries[3].tx, valid_child_valid);
|
||||
}
|
||||
}
|
||||
// Cleanup db directory
|
||||
clean_output_dir(db_root.clone());
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ fn test_the_transaction_pool() {
|
|||
|
||||
let db_root = ".grin_transaction_pool".to_string();
|
||||
clean_output_dir(db_root.clone());
|
||||
|
||||
let chain = Arc::new(ChainAdapter::init(db_root.clone()).unwrap());
|
||||
|
||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||
|
@ -235,19 +236,225 @@ fn test_the_transaction_pool() {
|
|||
// Check we cannot "double spend" an output spent in a previous block.
|
||||
// We use the initial coinbase output here for convenience.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
let chain = Arc::new(ChainAdapter::init(db_root.clone()).unwrap());
|
||||
|
||||
let double_spend_tx =
|
||||
{ test_transaction_spending_coinbase(&keychain, &header, vec![1000]) };
|
||||
let verifier_cache = Arc::new(RwLock::new(LruVerifierCache::new()));
|
||||
|
||||
// check we cannot add a double spend to the stempool
|
||||
assert!(write_pool
|
||||
.add_to_pool(test_source(), double_spend_tx.clone(), true, &header)
|
||||
.is_err());
|
||||
// Initialize a new pool with our chain adapter.
|
||||
let pool = RwLock::new(test_setup(chain.clone(), verifier_cache.clone()));
|
||||
|
||||
// check we cannot add a double spend to the txpool
|
||||
assert!(write_pool
|
||||
.add_to_pool(test_source(), double_spend_tx.clone(), false, &header)
|
||||
.is_err());
|
||||
let header = {
|
||||
let height = 1;
|
||||
let key_id = ExtKeychain::derive_key_id(1, height as u32, 0, 0, 0);
|
||||
let reward = libtx::reward::output(&keychain, &key_id, 0, false).unwrap();
|
||||
let block =
|
||||
Block::new(&BlockHeader::default(), vec![], Difficulty::min(), reward).unwrap();
|
||||
|
||||
chain.update_db_for_block(&block);
|
||||
|
||||
block.header
|
||||
};
|
||||
|
||||
// Now create tx to spend a coinbase, giving us some useful outputs for testing
|
||||
// with.
|
||||
let initial_tx = {
|
||||
test_transaction_spending_coinbase(
|
||||
&keychain,
|
||||
&header,
|
||||
vec![500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400],
|
||||
)
|
||||
};
|
||||
|
||||
// Add this tx to the pool (stem=false, direct to txpool).
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
write_pool
|
||||
.add_to_pool(test_source(), initial_tx, false, &header)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 1);
|
||||
}
|
||||
|
||||
// Test adding a tx that "double spends" an output currently spent by a tx
|
||||
// already in the txpool. In this case we attempt to spend the original coinbase twice.
|
||||
{
|
||||
let tx = test_transaction_spending_coinbase(&keychain, &header, vec![501]);
|
||||
let mut write_pool = pool.write();
|
||||
assert!(write_pool
|
||||
.add_to_pool(test_source(), tx, false, &header)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// tx1 spends some outputs from the initial test tx.
|
||||
let tx1 = test_transaction(&keychain, vec![500, 600], vec![499, 599]);
|
||||
// tx2 spends some outputs from both tx1 and the initial test tx.
|
||||
let tx2 = test_transaction(&keychain, vec![499, 700], vec![498]);
|
||||
|
||||
// Take a write lock and add a couple of tx entries to the pool.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
|
||||
// Check we have a single initial tx in the pool.
|
||||
assert_eq!(write_pool.total_size(), 1);
|
||||
|
||||
// First, add a simple tx directly to the txpool (stem = false).
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx1.clone(), false, &header)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 2);
|
||||
|
||||
// Add another tx spending outputs from the previous tx.
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx2.clone(), false, &header)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 3);
|
||||
}
|
||||
|
||||
// Test adding the exact same tx multiple times (same kernel signature).
|
||||
// This will fail for stem=false during tx aggregation due to duplicate
|
||||
// outputs and duplicate kernels.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
assert!(write_pool
|
||||
.add_to_pool(test_source(), tx1.clone(), false, &header)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// Test adding a duplicate tx with the same input and outputs.
|
||||
// Note: not the *same* tx, just same underlying inputs/outputs.
|
||||
{
|
||||
let tx1a = test_transaction(&keychain, vec![500, 600], vec![499, 599]);
|
||||
let mut write_pool = pool.write();
|
||||
assert!(write_pool
|
||||
.add_to_pool(test_source(), tx1a, false, &header)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// Test adding a tx attempting to spend a non-existent output.
|
||||
{
|
||||
let bad_tx = test_transaction(&keychain, vec![10_001], vec![10_000]);
|
||||
let mut write_pool = pool.write();
|
||||
assert!(write_pool
|
||||
.add_to_pool(test_source(), bad_tx, false, &header)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// Test adding a tx that would result in a duplicate output (conflicts with
|
||||
// output from tx2). For reasons of security all outputs in the UTXO set must
|
||||
// be unique. Otherwise spending one will almost certainly cause the other
|
||||
// to be immediately stolen via a "replay" tx.
|
||||
{
|
||||
let tx = test_transaction(&keychain, vec![900], vec![498]);
|
||||
let mut write_pool = pool.write();
|
||||
assert!(write_pool
|
||||
.add_to_pool(test_source(), tx, false, &header)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// Confirm the tx pool correctly identifies an invalid tx (already spent).
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
let tx3 = test_transaction(&keychain, vec![500], vec![497]);
|
||||
assert!(write_pool
|
||||
.add_to_pool(test_source(), tx3, false, &header)
|
||||
.is_err());
|
||||
assert_eq!(write_pool.total_size(), 3);
|
||||
}
|
||||
|
||||
// Now add a couple of txs to the stempool (stem = true).
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
let tx = test_transaction(&keychain, vec![599], vec![598]);
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx, true, &header)
|
||||
.unwrap();
|
||||
let tx2 = test_transaction(&keychain, vec![598], vec![597]);
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx2, true, &header)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 3);
|
||||
assert_eq!(write_pool.stempool.size(), 2);
|
||||
}
|
||||
|
||||
// Check we can take some entries from the stempool and "fluff" them into the
|
||||
// txpool. This also exercises multi-kernel txs.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
let agg_tx = write_pool
|
||||
.stempool
|
||||
.all_transactions_aggregate()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(agg_tx.kernels().len(), 2);
|
||||
write_pool
|
||||
.add_to_pool(test_source(), agg_tx, false, &header)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 4);
|
||||
assert!(write_pool.stempool.is_empty());
|
||||
}
|
||||
|
||||
// Adding a duplicate tx to the stempool will result in it being fluffed.
|
||||
// This handles the case of the stem path having a cycle in it.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
let tx = test_transaction(&keychain, vec![597], vec![596]);
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx.clone(), true, &header)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 4);
|
||||
assert_eq!(write_pool.stempool.size(), 1);
|
||||
|
||||
// Duplicate stem tx so fluff, adding it to txpool and removing it from stempool.
|
||||
write_pool
|
||||
.add_to_pool(test_source(), tx.clone(), true, &header)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 5);
|
||||
assert!(write_pool.stempool.is_empty());
|
||||
}
|
||||
|
||||
// Now check we can correctly deaggregate a multi-kernel tx based on current
|
||||
// contents of the txpool.
|
||||
// We will do this be adding a new tx to the pool
|
||||
// that is a superset of a tx already in the pool.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
|
||||
let tx4 = test_transaction(&keychain, vec![800], vec![799]);
|
||||
// tx1 and tx2 are already in the txpool (in aggregated form)
|
||||
// tx4 is the "new" part of this aggregated tx that we care about
|
||||
let agg_tx = transaction::aggregate(vec![tx1.clone(), tx2.clone(), tx4]).unwrap();
|
||||
|
||||
agg_tx
|
||||
.validate(Weighting::AsTransaction, verifier_cache.clone())
|
||||
.unwrap();
|
||||
|
||||
write_pool
|
||||
.add_to_pool(test_source(), agg_tx, false, &header)
|
||||
.unwrap();
|
||||
assert_eq!(write_pool.total_size(), 6);
|
||||
let entry = write_pool.txpool.entries.last().unwrap();
|
||||
assert_eq!(entry.tx.kernels().len(), 1);
|
||||
assert_eq!(entry.src.debug_name, "deagg");
|
||||
}
|
||||
|
||||
// Check we cannot "double spend" an output spent in a previous block.
|
||||
// We use the initial coinbase output here for convenience.
|
||||
{
|
||||
let mut write_pool = pool.write();
|
||||
|
||||
let double_spend_tx =
|
||||
{ test_transaction_spending_coinbase(&keychain, &header, vec![1000]) };
|
||||
|
||||
// check we cannot add a double spend to the stempool
|
||||
assert!(write_pool
|
||||
.add_to_pool(test_source(), double_spend_tx.clone(), true, &header)
|
||||
.is_err());
|
||||
|
||||
// check we cannot add a double spend to the txpool
|
||||
assert!(write_pool
|
||||
.add_to_pool(test_source(), double_spend_tx.clone(), false, &header)
|
||||
.is_err());
|
||||
}
|
||||
}
|
||||
// Cleanup db directory
|
||||
clean_output_dir(db_root.clone());
|
||||
}
|
||||
|
|
|
@ -55,22 +55,22 @@ pub struct NetToChainAdapter {
|
|||
}
|
||||
|
||||
impl p2p::ChainAdapter for NetToChainAdapter {
|
||||
fn total_difficulty(&self) -> Difficulty {
|
||||
self.chain().head().unwrap().total_difficulty
|
||||
fn total_difficulty(&self) -> Result<Difficulty, chain::Error> {
|
||||
Ok(self.chain().head()?.total_difficulty)
|
||||
}
|
||||
|
||||
fn total_height(&self) -> u64 {
|
||||
self.chain().head().unwrap().height
|
||||
fn total_height(&self) -> Result<u64, chain::Error> {
|
||||
Ok(self.chain().head()?.height)
|
||||
}
|
||||
|
||||
fn get_transaction(&self, kernel_hash: Hash) -> Option<core::Transaction> {
|
||||
self.tx_pool.read().retrieve_tx_by_kernel_hash(kernel_hash)
|
||||
}
|
||||
|
||||
fn tx_kernel_received(&self, kernel_hash: Hash, addr: PeerAddr) {
|
||||
fn tx_kernel_received(&self, kernel_hash: Hash, addr: PeerAddr) -> Result<bool, chain::Error> {
|
||||
// nothing much we can do with a new transaction while syncing
|
||||
if self.sync_state.is_syncing() {
|
||||
return;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let tx = self.tx_pool.read().retrieve_tx_by_kernel_hash(kernel_hash);
|
||||
|
@ -78,12 +78,17 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
if tx.is_none() {
|
||||
self.request_transaction(kernel_hash, addr);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn transaction_received(&self, tx: core::Transaction, stem: bool) {
|
||||
fn transaction_received(
|
||||
&self,
|
||||
tx: core::Transaction,
|
||||
stem: bool,
|
||||
) -> Result<bool, chain::Error> {
|
||||
// nothing much we can do with a new transaction while syncing
|
||||
if self.sync_state.is_syncing() {
|
||||
return;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let source = pool::TxSource {
|
||||
|
@ -91,7 +96,7 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
identifier: "?.?.?.?".to_string(),
|
||||
};
|
||||
|
||||
let header = self.chain().head_header().unwrap();
|
||||
let header = self.chain().head_header()?;
|
||||
|
||||
for hook in &self.hooks {
|
||||
hook.on_transaction_received(&tx);
|
||||
|
@ -99,26 +104,50 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
|
||||
let tx_hash = tx.hash();
|
||||
|
||||
let res = {
|
||||
let mut tx_pool = self.tx_pool.write();
|
||||
tx_pool.add_to_pool(source, tx, stem, &header)
|
||||
};
|
||||
|
||||
if let Err(e) = res {
|
||||
debug!("Transaction {} rejected: {:?}", tx_hash, e);
|
||||
let mut tx_pool = self.tx_pool.write();
|
||||
match tx_pool.add_to_pool(source, tx, stem, &header) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => {
|
||||
debug!("Transaction {} rejected: {:?}", tx_hash, e);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn block_received(&self, b: core::Block, addr: PeerAddr, was_requested: bool) -> bool {
|
||||
if !self.sync_state.is_syncing() {
|
||||
for hook in &self.hooks {
|
||||
hook.on_block_received(&b, &addr);
|
||||
}
|
||||
}
|
||||
fn block_received(
|
||||
&self,
|
||||
b: core::Block,
|
||||
addr: PeerAddr,
|
||||
was_requested: bool,
|
||||
) -> Result<bool, chain::Error> {
|
||||
debug!(
|
||||
"Received block {} at {} from {} [in/out/kern: {}/{}/{}] going to process.",
|
||||
b.hash(),
|
||||
b.header.height,
|
||||
addr,
|
||||
b.inputs().len(),
|
||||
b.outputs().len(),
|
||||
b.kernels().len(),
|
||||
);
|
||||
self.process_block(b, addr, was_requested)
|
||||
}
|
||||
|
||||
fn compact_block_received(&self, cb: core::CompactBlock, addr: PeerAddr) -> bool {
|
||||
fn compact_block_received(
|
||||
&self,
|
||||
cb: core::CompactBlock,
|
||||
addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
let bhash = cb.hash();
|
||||
debug!(
|
||||
"Received compact_block {} at {} from {} [out/kern/kern_ids: {}/{}/{}] going to process.",
|
||||
bhash,
|
||||
cb.header.height,
|
||||
addr,
|
||||
cb.out_full().len(),
|
||||
cb.kern_full().len(),
|
||||
cb.kern_ids().len(),
|
||||
);
|
||||
|
||||
let cb_hash = cb.hash();
|
||||
if cb.kern_ids().is_empty() {
|
||||
// push the freshly hydrated block through the chain pipeline
|
||||
|
@ -133,7 +162,7 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
}
|
||||
Err(e) => {
|
||||
debug!("Invalid hydrated block {}: {:?}", cb_hash, e);
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -143,7 +172,7 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
.process_block_header(&cb.header, self.chain_opts(false))
|
||||
{
|
||||
debug!("Invalid compact block header {}: {:?}", cb_hash, e.kind());
|
||||
return !e.is_bad_data();
|
||||
return Ok(!e.is_bad_data());
|
||||
}
|
||||
|
||||
let (txs, missing_short_ids) = {
|
||||
|
@ -174,7 +203,7 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
}
|
||||
Err(e) => {
|
||||
debug!("Invalid hydrated block {}: {:?}", cb.hash(), e);
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -189,25 +218,25 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
if self.sync_state.status() == SyncStatus::NoSync {
|
||||
debug!("adapter: block invalid after hydration, requesting full block");
|
||||
self.request_block(&cb.header, addr);
|
||||
true
|
||||
Ok(true)
|
||||
} else {
|
||||
debug!("block invalid after hydration, ignoring it, cause still syncing");
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!("failed to retrieve previous block header (still syncing?)");
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn header_received(&self, bh: core::BlockHeader, addr: PeerAddr) -> bool {
|
||||
if !self.sync_state.is_syncing() {
|
||||
for hook in &self.hooks {
|
||||
hook.on_header_received(&bh, &addr);
|
||||
}
|
||||
}
|
||||
fn header_received(&self, bh: core::BlockHeader, addr: PeerAddr) -> Result<bool, chain::Error> {
|
||||
let bhash = bh.hash();
|
||||
debug!(
|
||||
"Received block header {} at {} from {}, going to process.",
|
||||
bhash, bh.height, addr,
|
||||
);
|
||||
|
||||
// pushing the new block header through the header chain pipeline
|
||||
// we will go ask for the block if this is a new header
|
||||
|
@ -215,18 +244,14 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
.chain()
|
||||
.process_block_header(&bh, self.chain_opts(false));
|
||||
|
||||
if let &Err(ref e) = &res {
|
||||
debug!(
|
||||
"Block header {} refused by chain: {:?}",
|
||||
bh.hash(),
|
||||
e.kind()
|
||||
);
|
||||
if let Err(e) = res {
|
||||
debug!("Block header {} refused by chain: {:?}", bhash, e.kind());
|
||||
if e.is_bad_data() {
|
||||
return false;
|
||||
return Ok(false);
|
||||
} else {
|
||||
// we got an error when trying to process the block header
|
||||
// but nothing serious enough to need to ban the peer upstream
|
||||
return true;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,37 +260,43 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
self.request_compact_block(&bh, addr);
|
||||
|
||||
// done receiving the header
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn headers_received(&self, bhs: &[core::BlockHeader], addr: PeerAddr) -> bool {
|
||||
fn headers_received(
|
||||
&self,
|
||||
bhs: &[core::BlockHeader],
|
||||
addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
info!("Received {} block headers from {}", bhs.len(), addr,);
|
||||
|
||||
if bhs.len() == 0 {
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// try to add headers to our header chain
|
||||
let res = self.chain().sync_block_headers(bhs, self.chain_opts(true));
|
||||
if let &Err(ref e) = &res {
|
||||
debug!("Block headers refused by chain: {:?}", e);
|
||||
|
||||
if e.is_bad_data() {
|
||||
return false;
|
||||
match self.chain().sync_block_headers(bhs, self.chain_opts(true)) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => {
|
||||
debug!("Block headers refused by chain: {:?}", e);
|
||||
if e.is_bad_data() {
|
||||
return Ok(false);
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn locate_headers(&self, locator: &[Hash]) -> Vec<core::BlockHeader> {
|
||||
fn locate_headers(&self, locator: &[Hash]) -> Result<Vec<core::BlockHeader>, chain::Error> {
|
||||
debug!("locator: {:?}", locator);
|
||||
|
||||
let header = match self.find_common_header(locator) {
|
||||
Some(header) => header,
|
||||
None => return vec![],
|
||||
None => return Ok(vec![]),
|
||||
};
|
||||
|
||||
let max_height = self.chain().header_head().unwrap().height;
|
||||
let max_height = self.chain().header_head()?.height;
|
||||
|
||||
let txhashset = self.chain().txhashset();
|
||||
let txhashset = txhashset.read();
|
||||
|
@ -288,7 +319,7 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
|
||||
debug!("returning headers: {}", headers.len());
|
||||
|
||||
headers
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
/// Gets a full block by its hash.
|
||||
|
@ -353,11 +384,16 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
/// If we're willing to accept that new state, the data stream will be
|
||||
/// read as a zip file, unzipped and the resulting state files should be
|
||||
/// rewound to the provided indexes.
|
||||
fn txhashset_write(&self, h: Hash, txhashset_data: File, _peer_addr: PeerAddr) -> bool {
|
||||
fn txhashset_write(
|
||||
&self,
|
||||
h: Hash,
|
||||
txhashset_data: File,
|
||||
_peer_addr: PeerAddr,
|
||||
) -> Result<bool, chain::Error> {
|
||||
// check status again after download, in case 2 txhashsets made it somehow
|
||||
if let SyncStatus::TxHashsetDownload { .. } = self.sync_state.status() {
|
||||
} else {
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if let Err(e) = self
|
||||
|
@ -366,12 +402,13 @@ impl p2p::ChainAdapter for NetToChainAdapter {
|
|||
{
|
||||
self.chain().clean_txhashset_sandbox();
|
||||
error!("Failed to save txhashset archive: {}", e);
|
||||
|
||||
let is_good_data = !e.is_bad_data();
|
||||
self.sync_state.set_sync_error(types::Error::Chain(e));
|
||||
is_good_data
|
||||
Ok(is_good_data)
|
||||
} else {
|
||||
info!("Received valid txhashset data for {}.", h);
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -435,15 +472,20 @@ impl NetToChainAdapter {
|
|||
|
||||
// pushing the new block through the chain pipeline
|
||||
// remembering to reset the head if we have a bad block
|
||||
fn process_block(&self, b: core::Block, addr: PeerAddr, was_requested: bool) -> bool {
|
||||
fn process_block(
|
||||
&self,
|
||||
b: core::Block,
|
||||
addr: PeerAddr,
|
||||
was_requested: bool,
|
||||
) -> Result<bool, chain::Error> {
|
||||
// We cannot process blocks earlier than the horizon so check for this here.
|
||||
{
|
||||
let head = self.chain().head().unwrap();
|
||||
let head = self.chain().head()?;
|
||||
let horizon = head
|
||||
.height
|
||||
.saturating_sub(global::cut_through_horizon() as u64);
|
||||
if b.header.height < horizon {
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -457,11 +499,11 @@ impl NetToChainAdapter {
|
|||
Ok(_) => {
|
||||
self.validate_chain(bhash);
|
||||
self.check_compact();
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
Err(ref e) if e.is_bad_data() => {
|
||||
self.validate_chain(bhash);
|
||||
false
|
||||
Ok(false)
|
||||
}
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
|
@ -475,7 +517,7 @@ impl NetToChainAdapter {
|
|||
self.request_block_by_hash(previous.hash(), addr)
|
||||
}
|
||||
}
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
_ => {
|
||||
debug!(
|
||||
|
@ -483,7 +525,7 @@ impl NetToChainAdapter {
|
|||
bhash,
|
||||
e.kind()
|
||||
);
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,8 +125,12 @@ pub fn connect_and_monitor(
|
|||
if Utc::now() - prev_ping > Duration::seconds(10) {
|
||||
let total_diff = peers.total_difficulty();
|
||||
let total_height = peers.total_height();
|
||||
peers.check_all(total_diff, total_height);
|
||||
prev_ping = Utc::now();
|
||||
if total_diff.is_ok() && total_height.is_ok() {
|
||||
peers.check_all(total_diff.unwrap(), total_height.unwrap());
|
||||
prev_ping = Utc::now();
|
||||
} else {
|
||||
error!("failed to get peers difficulty and/or height");
|
||||
}
|
||||
}
|
||||
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
|
|
|
@ -100,7 +100,7 @@ impl BodySync {
|
|||
|
||||
hashes.reverse();
|
||||
|
||||
let peers = self.peers.more_work_peers();
|
||||
let peers = self.peers.more_work_peers()?;
|
||||
|
||||
// if we have 5 peers to sync from then ask for 50 blocks total (peer_count *
|
||||
// 10) max will be 80 if all 8 peers are advertising more work
|
||||
|
|
|
@ -77,7 +77,7 @@ impl SyncRunner {
|
|||
let mut n = 0;
|
||||
const MIN_PEERS: usize = 3;
|
||||
loop {
|
||||
let wp = self.peers.more_or_same_work_peers();
|
||||
let wp = self.peers.more_or_same_work_peers()?;
|
||||
// exit loop when:
|
||||
// * we have more than MIN_PEERS more_or_same_work peers
|
||||
// * we are synced already, e.g. grin was quickly restarted
|
||||
|
@ -146,16 +146,27 @@ impl SyncRunner {
|
|||
|
||||
thread::sleep(time::Duration::from_millis(10));
|
||||
|
||||
let currently_syncing = self.sync_state.is_syncing();
|
||||
|
||||
// check whether syncing is generally needed, when we compare our state with others
|
||||
let (syncing, most_work_height) = unwrap_or_restart_loop!(self.needs_syncing());
|
||||
let (needs_syncing, most_work_height) = unwrap_or_restart_loop!(self.needs_syncing());
|
||||
if most_work_height > 0 {
|
||||
// we can occasionally get a most work height of 0 if read locks fail
|
||||
highest_height = most_work_height;
|
||||
}
|
||||
|
||||
// quick short-circuit (and a decent sleep) if no syncing is needed
|
||||
if !syncing {
|
||||
self.sync_state.update(SyncStatus::NoSync);
|
||||
if !needs_syncing {
|
||||
if currently_syncing {
|
||||
self.sync_state.update(SyncStatus::NoSync);
|
||||
|
||||
// Initial transition out of a "syncing" state and into NoSync.
|
||||
// This triggers a chain compaction to keep out local node tidy.
|
||||
// Note: Chain compaction runs with an internal threshold
|
||||
// so can be safely run even if the node is restarted frequently.
|
||||
unwrap_or_restart_loop!(self.chain.compact());
|
||||
}
|
||||
|
||||
thread::sleep(time::Duration::from_secs(10));
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -84,12 +84,15 @@ pub fn get_block(
|
|||
wallet_listener_url.clone(),
|
||||
);
|
||||
while let Err(e) = result {
|
||||
let mut new_key_id = key_id.to_owned();
|
||||
match e {
|
||||
self::Error::Chain(c) => match c.kind() {
|
||||
chain::ErrorKind::DuplicateCommitment(_) => {
|
||||
debug!(
|
||||
"Duplicate commit for potential coinbase detected. Trying next derivation."
|
||||
);
|
||||
// use the next available key to generate a different coinbase commitment
|
||||
new_key_id = None;
|
||||
}
|
||||
_ => {
|
||||
error!("Chain Error: {}", c);
|
||||
|
@ -106,12 +109,18 @@ pub fn get_block(
|
|||
warn!("Error building new block: {:?}. Retrying.", ae);
|
||||
}
|
||||
}
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
|
||||
// only wait if we are still using the same key: a different coinbase commitment is unlikely
|
||||
// to have duplication
|
||||
if new_key_id.is_some() {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
result = build_block(
|
||||
chain,
|
||||
tx_pool,
|
||||
verifier_cache.clone(),
|
||||
key_id.clone(),
|
||||
new_key_id,
|
||||
wallet_listener_url.clone(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -62,6 +62,10 @@ pub fn client_command(client_args: &ArgMatches<'_>, global_config: GlobalConfig)
|
|||
pub fn show_status(config: &ServerConfig, api_secret: Option<String>) {
|
||||
println!();
|
||||
let title = format!("Grin Server Status");
|
||||
if term::stdout().is_none() {
|
||||
println!("Could not open terminal");
|
||||
return;
|
||||
}
|
||||
let mut t = term::stdout().unwrap();
|
||||
let mut e = term::stdout().unwrap();
|
||||
t.fg(term::color::MAGENTA).unwrap();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: grin
|
||||
version: "1.0.2"
|
||||
version: "1.0.3"
|
||||
about: Lightweight implementation of the MimbleWimble protocol.
|
||||
author: The Grin Team
|
||||
|
||||
|
|
|
@ -284,7 +284,7 @@ impl<T: PMMRable> PMMRBackend<T> {
|
|||
Ok(())
|
||||
.and(self.hash_file.flush())
|
||||
.and(self.data_file.flush())
|
||||
.and(self.leaf_set.flush())
|
||||
.and(self.sync_leaf_set())
|
||||
.map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Interrupted,
|
||||
|
@ -293,11 +293,19 @@ impl<T: PMMRable> PMMRBackend<T> {
|
|||
})
|
||||
}
|
||||
|
||||
// Sync the leaf_set if this is a prunable backend.
|
||||
fn sync_leaf_set(&mut self) -> io::Result<()> {
|
||||
if !self.prunable {
|
||||
return Ok(());
|
||||
}
|
||||
self.leaf_set.flush()
|
||||
}
|
||||
|
||||
/// Discard the current, non synced state of the backend.
|
||||
pub fn discard(&mut self) {
|
||||
self.hash_file.discard();
|
||||
self.leaf_set.discard();
|
||||
self.data_file.discard();
|
||||
self.leaf_set.discard();
|
||||
}
|
||||
|
||||
/// Takes the leaf_set at a given cutoff_pos and generates an updated
|
||||
|
@ -323,7 +331,8 @@ impl<T: PMMRable> PMMRBackend<T> {
|
|||
pos as u64 - shift
|
||||
});
|
||||
|
||||
self.hash_file.save_prune(&pos_to_rm)?;
|
||||
self.hash_file
|
||||
.save_prune(&pos_to_rm)?;
|
||||
}
|
||||
|
||||
// 2. Save compact copy of the data file, skipping removed leaves.
|
||||
|
|
|
@ -59,7 +59,7 @@ impl Writeable for SizeEntry {
|
|||
}
|
||||
}
|
||||
|
||||
/// Data file (MMR) wrapper around an append-only file.
|
||||
/// Data file (MMR) wrapper around an append only file.
|
||||
pub struct DataFile<T> {
|
||||
file: AppendOnlyFile<T>,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue