mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
verify coinbase maturity via output_mmr_size (#1203)
* we do not need Merkle proofs to spend coinbase outputs we only need the output_mmr_size from the block header * tests working with no Merkle proofs in inputs
This commit is contained in:
parent
d0f8d325f2
commit
1df409fa69
21 changed files with 61 additions and 443 deletions
|
@ -39,8 +39,6 @@ const HEADER_HEAD_PREFIX: u8 = 'I' as u8;
|
|||
const SYNC_HEAD_PREFIX: u8 = 's' as u8;
|
||||
const HEADER_HEIGHT_PREFIX: u8 = '8' as u8;
|
||||
const COMMIT_POS_PREFIX: u8 = 'c' as u8;
|
||||
const BLOCK_MARKER_PREFIX: u8 = 'm' as u8;
|
||||
const BLOCK_SUMS_PREFIX: u8 = 'M' as u8;
|
||||
const BLOCK_INPUT_BITMAP_PREFIX: u8 = 'B' as u8;
|
||||
|
||||
/// All chain-related database operations
|
||||
|
|
|
@ -564,36 +564,39 @@ impl<'a> Extension<'a> {
|
|||
inputs: &Vec<Input>,
|
||||
height: u64,
|
||||
) -> Result<(), Error> {
|
||||
for input in inputs {
|
||||
if input.features.contains(OutputFeatures::COINBASE_OUTPUT) {
|
||||
self.verify_maturity_via_merkle_proof(input, height)?;
|
||||
// Find the greatest output pos of any coinbase
|
||||
// outputs we are attempting to spend.
|
||||
let pos = inputs
|
||||
.iter()
|
||||
.filter(|x| x.features.contains(OutputFeatures::COINBASE_OUTPUT))
|
||||
.filter_map(|x| self.commit_index.get_output_pos(&x.commitment()).ok())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
if pos > 0 {
|
||||
// If we have not yet reached 1,000 blocks then
|
||||
// we can fail immediately as coinbase cannot be mature.
|
||||
if height < global::coinbase_maturity() {
|
||||
return Err(Error::ImmatureCoinbase);
|
||||
}
|
||||
|
||||
// Find the "cutoff" pos in the output MMR based on the
|
||||
// header from 1,000 blocks ago.
|
||||
let cutoff_height = height.checked_sub(global::coinbase_maturity()).unwrap_or(0);
|
||||
let cutoff_header = self.commit_index.get_header_by_height(cutoff_height)?;
|
||||
let cutoff_pos = cutoff_header.output_mmr_size;
|
||||
|
||||
|
||||
// If any output pos exceeed the cutoff_pos
|
||||
// we know they have not yet sufficiently matured.
|
||||
if pos > cutoff_pos {
|
||||
return Err(Error::ImmatureCoinbase);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_maturity_via_merkle_proof(&self, input: &Input, height: u64) -> Result<(), Error> {
|
||||
let header = self.commit_index.get_block_header(&input.block_hash())?;
|
||||
|
||||
// Check that the height indicates it has matured sufficiently
|
||||
// we will check the Merkle proof below to ensure we are being
|
||||
// honest about the height
|
||||
if header.height + global::coinbase_maturity() >= height {
|
||||
return Err(Error::ImmatureCoinbase);
|
||||
}
|
||||
|
||||
// We need the MMR pos to verify the Merkle proof
|
||||
let pos = self.get_output_pos(&input.commitment())?;
|
||||
|
||||
let out_id = OutputIdentifier::from_input(input);
|
||||
let res = input
|
||||
.merkle_proof()
|
||||
.verify(header.output_root, &out_id, pos)
|
||||
.map_err(|_| Error::MerkleProof)?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Apply a new set of blocks on top the existing sum trees. Blocks are
|
||||
/// applied in order of the provided Vec. If pruning is enabled, inputs also
|
||||
/// prune MMR data.
|
||||
|
|
|
@ -18,13 +18,12 @@ use std::{error, fmt, io};
|
|||
|
||||
use util::secp;
|
||||
use util::secp::pedersen::Commitment;
|
||||
use util::secp_static;
|
||||
|
||||
use core::core::committed;
|
||||
use core::core::hash::{Hash, Hashed};
|
||||
use core::core::target::Difficulty;
|
||||
use core::core::{block, transaction, Block, BlockHeader};
|
||||
use core::ser::{self, Readable, Reader, Writeable, Writer};
|
||||
use core::ser;
|
||||
use grin_store as store;
|
||||
use keychain;
|
||||
|
||||
|
|
|
@ -263,8 +263,6 @@ fn spend_in_fork_and_compact() {
|
|||
vec![
|
||||
build::coinbase_input(
|
||||
consensus::REWARD,
|
||||
block_hash,
|
||||
merkle_proof,
|
||||
kc.derive_key_id(2).unwrap(),
|
||||
),
|
||||
build::output(consensus::REWARD - 20000, kc.derive_key_id(30).unwrap()),
|
||||
|
|
|
@ -106,7 +106,7 @@ fn test_coinbase_maturity() {
|
|||
// this is not a valid tx as the coinbase output cannot be spent yet
|
||||
let coinbase_txn = build::transaction(
|
||||
vec![
|
||||
build::coinbase_input(amount, block_hash, merkle_proof.clone(), key_id1.clone()),
|
||||
build::coinbase_input(amount, key_id1.clone()),
|
||||
build::output(amount - 2, key_id2.clone()),
|
||||
build::with_fee(2),
|
||||
],
|
||||
|
|
|
@ -81,8 +81,6 @@ fn test_some_raw_txs() {
|
|||
vec![
|
||||
build::coinbase_input(
|
||||
coinbase_reward,
|
||||
block.hash(),
|
||||
MerkleProof::default(),
|
||||
key_id1.clone(),
|
||||
),
|
||||
build::output(100, key_id2.clone()),
|
||||
|
@ -97,8 +95,6 @@ fn test_some_raw_txs() {
|
|||
vec![
|
||||
build::coinbase_input(
|
||||
coinbase_reward,
|
||||
block.hash(),
|
||||
MerkleProof::default(),
|
||||
key_id1.clone(),
|
||||
),
|
||||
build::output(100, key_id4.clone()),
|
||||
|
|
|
@ -17,19 +17,17 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::cmp::max;
|
||||
use std::collections::HashSet;
|
||||
use std::io::Cursor;
|
||||
use std::{error, fmt};
|
||||
|
||||
use util::secp::pedersen::{Commitment, ProofMessage, RangeProof};
|
||||
use util::secp::pedersen::{Commitment, RangeProof};
|
||||
use util::secp::{self, Message, Signature};
|
||||
use util::{kernel_sig_msg, static_secp_instance};
|
||||
|
||||
use consensus::{self, VerifySortOrder};
|
||||
use core::hash::{Hash, Hashed, ZERO_HASH};
|
||||
use core::merkle_proof::MerkleProof;
|
||||
use core::hash::Hashed;
|
||||
use core::{committed, Committed};
|
||||
use keychain::{self, BlindingFactor};
|
||||
use ser::{self, read_and_verify_sorted, ser_vec, PMMRable, Readable, Reader, Writeable,
|
||||
use ser::{self, read_and_verify_sorted, PMMRable, Readable, Reader, Writeable,
|
||||
WriteableSorted, Writer};
|
||||
use util;
|
||||
|
||||
|
@ -441,22 +439,13 @@ impl Transaction {
|
|||
Transaction::weight(
|
||||
self.inputs.len(),
|
||||
self.outputs.len(),
|
||||
self.input_proofs_count(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Collect input's Merkle proofs
|
||||
pub fn input_proofs_count(&self) -> usize {
|
||||
self.inputs
|
||||
.iter()
|
||||
.filter(|i| i.merkle_proof.is_some())
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Calculate transaction weight from transaction details
|
||||
pub fn weight(input_len: usize, output_len: usize, proof_len: usize) -> u32 {
|
||||
pub fn weight(input_len: usize, output_len: usize) -> u32 {
|
||||
let mut tx_weight =
|
||||
-1 * (input_len as i32) + (4 * output_len as i32) + (proof_len as i32) + 1;
|
||||
-1 * (input_len as i32) + (4 * output_len as i32) + 1;
|
||||
if tx_weight < 1 {
|
||||
tx_weight = 1;
|
||||
}
|
||||
|
@ -636,13 +625,6 @@ pub struct Input {
|
|||
pub features: OutputFeatures,
|
||||
/// The commit referencing the output being spent.
|
||||
pub commit: Commitment,
|
||||
/// The hash of the block the output originated from.
|
||||
/// Currently we only care about this for coinbase outputs.
|
||||
pub block_hash: Option<Hash>,
|
||||
/// The Merkle Proof that shows the output being spent by this input
|
||||
/// existed and was unspent at the time of this block (proof of inclusion
|
||||
/// in output_root)
|
||||
pub merkle_proof: Option<MerkleProof>,
|
||||
}
|
||||
|
||||
hashable_ord!(Input);
|
||||
|
@ -663,17 +645,6 @@ impl Writeable for Input {
|
|||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(self.features.bits())?;
|
||||
self.commit.write(writer)?;
|
||||
|
||||
if writer.serialization_mode() != ser::SerializationMode::Hash {
|
||||
if self.features.contains(OutputFeatures::COINBASE_OUTPUT) {
|
||||
let block_hash = &self.block_hash.unwrap_or(ZERO_HASH);
|
||||
let merkle_proof = self.merkle_proof();
|
||||
|
||||
writer.write_fixed_bytes(block_hash)?;
|
||||
merkle_proof.write(writer)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -687,13 +658,7 @@ impl Readable for Input {
|
|||
|
||||
let commit = Commitment::read(reader)?;
|
||||
|
||||
if features.contains(OutputFeatures::COINBASE_OUTPUT) {
|
||||
let block_hash = Some(Hash::read(reader)?);
|
||||
let merkle_proof = Some(MerkleProof::read(reader)?);
|
||||
Ok(Input::new(features, commit, block_hash, merkle_proof))
|
||||
} else {
|
||||
Ok(Input::new(features, commit, None, None))
|
||||
}
|
||||
Ok(Input::new(features, commit))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -707,14 +672,10 @@ impl Input {
|
|||
pub fn new(
|
||||
features: OutputFeatures,
|
||||
commit: Commitment,
|
||||
block_hash: Option<Hash>,
|
||||
merkle_proof: Option<MerkleProof>,
|
||||
) -> Input {
|
||||
Input {
|
||||
features,
|
||||
commit,
|
||||
block_hash,
|
||||
merkle_proof,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -725,21 +686,6 @@ impl Input {
|
|||
pub fn commitment(&self) -> Commitment {
|
||||
self.commit
|
||||
}
|
||||
|
||||
/// Convenience function to return the (optional) block_hash for this input.
|
||||
/// Will return the default hash if we do not have one.
|
||||
pub fn block_hash(&self) -> Hash {
|
||||
self.block_hash.unwrap_or_else(Hash::default)
|
||||
}
|
||||
|
||||
/// Convenience function to return the (optional) merkle_proof for this
|
||||
/// input. Will return the "empty" Merkle proof if we do not have one.
|
||||
/// We currently only care about the Merkle proof for inputs spending
|
||||
/// coinbase outputs.
|
||||
pub fn merkle_proof(&self) -> MerkleProof {
|
||||
let merkle_proof = self.merkle_proof.clone();
|
||||
merkle_proof.unwrap_or_else(MerkleProof::empty)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
@ -920,6 +866,7 @@ impl Readable for OutputIdentifier {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use core::hash::Hash;
|
||||
use core::id::{ShortId, ShortIdentifiable};
|
||||
use keychain::{ExtKeychain, Keychain};
|
||||
use util::secp;
|
||||
|
@ -991,8 +938,6 @@ mod test {
|
|||
let input = Input {
|
||||
features: OutputFeatures::DEFAULT_OUTPUT,
|
||||
commit: commit,
|
||||
block_hash: None,
|
||||
merkle_proof: None,
|
||||
};
|
||||
|
||||
let block_hash = Hash::from_hex(
|
||||
|
@ -1009,8 +954,6 @@ mod test {
|
|||
let input = Input {
|
||||
features: OutputFeatures::COINBASE_OUTPUT,
|
||||
commit: commit,
|
||||
block_hash: None,
|
||||
merkle_proof: None,
|
||||
};
|
||||
|
||||
let short_id = input.short_id(&block_hash, nonce);
|
||||
|
|
|
@ -126,8 +126,6 @@ where
|
|||
let key_id = keychain.derive_key_id(header.height as u32).unwrap();
|
||||
tx_elements.push(libtx::build::coinbase_input(
|
||||
coinbase_reward,
|
||||
header.hash(),
|
||||
MerkleProof::default(),
|
||||
key_id,
|
||||
));
|
||||
}
|
||||
|
|
|
@ -207,76 +207,3 @@ pub fn get_outputs_by_pmmr_index(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get any missing block hashes from node
|
||||
pub fn get_missing_block_hashes_from_node(
|
||||
addr: &str,
|
||||
height: u64,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<
|
||||
(
|
||||
HashMap<pedersen::Commitment, (u64, BlockIdentifier)>,
|
||||
HashMap<pedersen::Commitment, MerkleProofWrapper>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let id_params: Vec<String> = wallet_outputs
|
||||
.iter()
|
||||
.map(|commit| format!("id={}", util::to_hex(commit.as_ref().to_vec())))
|
||||
.collect();
|
||||
|
||||
let height_params = [format!("start_height={}&end_height={}", 0, height)];
|
||||
let mut api_blocks: HashMap<pedersen::Commitment, (u64, BlockIdentifier)> = HashMap::new();
|
||||
let mut api_merkle_proofs: HashMap<pedersen::Commitment, MerkleProofWrapper> = HashMap::new();
|
||||
|
||||
// Split up into separate requests, to avoid hitting http limits
|
||||
for mut query_chunk in id_params.chunks(1000) {
|
||||
let url = format!(
|
||||
"{}/v1/chain/outputs/byheight?{}",
|
||||
addr,
|
||||
[&height_params, query_chunk].concat().join("&"),
|
||||
);
|
||||
|
||||
match api::client::get::<Vec<api::BlockOutputs>>(url.as_str()) {
|
||||
Ok(blocks) => for block in blocks {
|
||||
for out in block.outputs {
|
||||
api_blocks.insert(
|
||||
out.commit,
|
||||
(
|
||||
block.header.height,
|
||||
BlockIdentifier::from_hex(&block.header.hash).unwrap(),
|
||||
),
|
||||
);
|
||||
if let Some(merkle_proof) = out.merkle_proof {
|
||||
let wrapper = MerkleProofWrapper(merkle_proof);
|
||||
api_merkle_proofs.insert(out.commit, wrapper);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// if we got anything other than 200 back from server, bye
|
||||
return Err(e)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((api_blocks, api_merkle_proofs))
|
||||
}
|
||||
|
||||
/// Create a merkle proof at the given height for the given commit
|
||||
pub fn create_merkle_proof(addr: &str, commit: &str) -> Result<MerkleProofWrapper, Error> {
|
||||
let url = format!("{}/v1/txhashset/merkleproof?id={}", addr, commit);
|
||||
|
||||
match api::client::get::<api::OutputPrintable>(url.as_str()) {
|
||||
Ok(output) => Ok(MerkleProofWrapper(output.merkle_proof.unwrap())),
|
||||
Err(e) => {
|
||||
// if we got anything other than 200 back from server, bye
|
||||
error!(
|
||||
LOGGER,
|
||||
"get_merkle_proof_for_pos: Restore failed... unable to create merkle proof for commit {}. Error: {}",
|
||||
commit,
|
||||
e
|
||||
);
|
||||
Err(e)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ use client;
|
|||
use libtx::slate::Slate;
|
||||
use libwallet;
|
||||
use libwallet::types::{
|
||||
BlockFees, BlockIdentifier, CbData, MerkleProofWrapper, OutputData, TxWrapper, WalletBackend,
|
||||
BlockFees, BlockIdentifier, CbData, OutputData, TxWrapper, WalletBackend,
|
||||
WalletClient, WalletDetails, WalletOutputBatch,
|
||||
};
|
||||
use types::{WalletConfig, WalletSeed};
|
||||
|
@ -400,31 +400,6 @@ impl<K> WalletClient for FileWallet<K> {
|
|||
.context(libwallet::ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get any missing block hashes from node
|
||||
fn get_missing_block_hashes_from_node(
|
||||
&self,
|
||||
height: u64,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<
|
||||
(
|
||||
HashMap<pedersen::Commitment, (u64, BlockIdentifier)>,
|
||||
HashMap<pedersen::Commitment, MerkleProofWrapper>,
|
||||
),
|
||||
libwallet::Error,
|
||||
> {
|
||||
let res =
|
||||
client::get_missing_block_hashes_from_node(self.node_url(), height, wallet_outputs)
|
||||
.context(libwallet::ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// retrieve merkle proof for a commit from a node
|
||||
fn create_merkle_proof(&self, commit: &str) -> Result<MerkleProofWrapper, libwallet::Error> {
|
||||
let res = client::create_merkle_proof(self.node_url(), commit)
|
||||
.context(libwallet::ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> FileWallet<K>
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
use util::{kernel_sig_msg, secp};
|
||||
|
||||
use core::core::hash::Hash;
|
||||
use core::core::merkle_proof::MerkleProof;
|
||||
use core::core::{Input, Output, OutputFeatures, Transaction, TxKernel};
|
||||
use keychain::{self, BlindSum, BlindingFactor, Identifier, Keychain};
|
||||
use libtx::{aggsig, proof};
|
||||
|
@ -52,8 +51,6 @@ pub type Append<K: Keychain> = for<'a> Fn(&'a mut Context<K>, (Transaction, TxKe
|
|||
fn build_input<K>(
|
||||
value: u64,
|
||||
features: OutputFeatures,
|
||||
block_hash: Option<Hash>,
|
||||
merkle_proof: Option<MerkleProof>,
|
||||
key_id: Identifier,
|
||||
) -> Box<Append<K>>
|
||||
where
|
||||
|
@ -62,7 +59,7 @@ where
|
|||
Box::new(
|
||||
move |build, (tx, kern, sum)| -> (Transaction, TxKernel, BlindSum) {
|
||||
let commit = build.keychain.commit(value, &key_id).unwrap();
|
||||
let input = Input::new(features, commit, block_hash.clone(), merkle_proof.clone());
|
||||
let input = Input::new(features, commit);
|
||||
(tx.with_input(input), kern, sum.sub_key_id(key_id.clone()))
|
||||
},
|
||||
)
|
||||
|
@ -78,15 +75,12 @@ where
|
|||
LOGGER,
|
||||
"Building input (spending regular output): {}, {}", value, key_id
|
||||
);
|
||||
build_input(value, OutputFeatures::DEFAULT_OUTPUT, None, None, key_id)
|
||||
build_input(value, OutputFeatures::DEFAULT_OUTPUT, key_id)
|
||||
}
|
||||
|
||||
/// Adds a coinbase input spending a coinbase output.
|
||||
/// We will use the block hash to verify coinbase maturity.
|
||||
pub fn coinbase_input<K>(
|
||||
value: u64,
|
||||
block_hash: Hash,
|
||||
merkle_proof: MerkleProof,
|
||||
key_id: Identifier,
|
||||
) -> Box<Append<K>>
|
||||
where
|
||||
|
@ -96,13 +90,7 @@ where
|
|||
LOGGER,
|
||||
"Building input (spending coinbase): {}, {}", value, key_id
|
||||
);
|
||||
build_input(
|
||||
value,
|
||||
OutputFeatures::COINBASE_OUTPUT,
|
||||
Some(block_hash),
|
||||
Some(merkle_proof),
|
||||
key_id,
|
||||
)
|
||||
build_input(value, OutputFeatures::COINBASE_OUTPUT, key_id)
|
||||
}
|
||||
|
||||
/// Adds an output with the provided value and key identifier from the
|
||||
|
|
|
@ -36,11 +36,11 @@ pub use libtx::error::{Error, ErrorKind};
|
|||
const DEFAULT_BASE_FEE: u64 = consensus::MILLI_GRIN;
|
||||
|
||||
/// Transaction fee calculation
|
||||
pub fn tx_fee(input_len: usize, output_len: usize, proof_len: usize, base_fee: Option<u64>) -> u64 {
|
||||
pub fn tx_fee(input_len: usize, output_len: usize, base_fee: Option<u64>) -> u64 {
|
||||
let use_base_fee = match base_fee {
|
||||
Some(bf) => bf,
|
||||
None => DEFAULT_BASE_FEE,
|
||||
};
|
||||
|
||||
(Transaction::weight(input_len, output_len, proof_len) as u64) * use_base_fee
|
||||
(Transaction::weight(input_len, output_len) as u64) * use_base_fee
|
||||
}
|
||||
|
|
|
@ -264,7 +264,6 @@ impl Slate {
|
|||
let fee = tx_fee(
|
||||
self.tx.inputs.len(),
|
||||
self.tx.outputs.len(),
|
||||
self.tx.input_proofs_count(),
|
||||
None,
|
||||
);
|
||||
if fee > self.tx.fee() {
|
||||
|
|
|
@ -108,7 +108,8 @@ where
|
|||
Err(e) => {
|
||||
error!(
|
||||
LOGGER,
|
||||
"Communication with receiver failed on SenderInitiation send. Aborting transaction"
|
||||
"Communication with receiver failed on SenderInitiation send. Aborting transaction {:?}",
|
||||
e,
|
||||
);
|
||||
return Err(e)?;
|
||||
}
|
||||
|
|
|
@ -38,8 +38,6 @@ struct OutputResult {
|
|||
///
|
||||
pub is_coinbase: bool,
|
||||
///
|
||||
pub merkle_proof: Option<MerkleProofWrapper>,
|
||||
///
|
||||
pub blinding: SecretKey,
|
||||
}
|
||||
|
||||
|
@ -75,13 +73,8 @@ where
|
|||
"Output found: {:?}, amount: {:?}", commit, info.value
|
||||
);
|
||||
|
||||
let mut merkle_proof = None;
|
||||
let commit_str = util::to_hex(commit.as_ref().to_vec());
|
||||
|
||||
if *is_coinbase {
|
||||
merkle_proof = Some(wallet.create_merkle_proof(&commit_str)?);
|
||||
}
|
||||
|
||||
let height = current_chain_height;
|
||||
let lock_height = if *is_coinbase {
|
||||
height + global::coinbase_maturity()
|
||||
|
@ -97,7 +90,6 @@ where
|
|||
height: height,
|
||||
lock_height: lock_height,
|
||||
is_coinbase: *is_coinbase,
|
||||
merkle_proof: merkle_proof,
|
||||
blinding: info.blinding,
|
||||
});
|
||||
}
|
||||
|
@ -219,8 +211,6 @@ where
|
|||
height: output.height,
|
||||
lock_height: output.lock_height,
|
||||
is_coinbase: output.is_coinbase,
|
||||
block: None,
|
||||
merkle_proof: output.merkle_proof,
|
||||
});
|
||||
} else {
|
||||
warn!(
|
||||
|
|
|
@ -109,8 +109,6 @@ where
|
|||
height: current_height,
|
||||
lock_height: 0,
|
||||
is_coinbase: false,
|
||||
block: None,
|
||||
merkle_proof: None,
|
||||
});
|
||||
}
|
||||
batch.commit()?;
|
||||
|
@ -174,8 +172,6 @@ where
|
|||
height: height,
|
||||
lock_height: 0,
|
||||
is_coinbase: false,
|
||||
block: None,
|
||||
merkle_proof: None,
|
||||
});
|
||||
batch.commit()?;
|
||||
Ok(())
|
||||
|
@ -238,7 +234,7 @@ where
|
|||
// sender
|
||||
let mut fee;
|
||||
// First attempt to spend without change
|
||||
fee = tx_fee(coins.len(), 1, coins_proof_count(&coins), None);
|
||||
fee = tx_fee(coins.len(), 1, None);
|
||||
let mut total: u64 = coins.iter().map(|c| c.value).sum();
|
||||
let mut amount_with_fee = amount + fee;
|
||||
|
||||
|
@ -251,7 +247,7 @@ where
|
|||
|
||||
// Check if we need to use a change address
|
||||
if total > amount_with_fee {
|
||||
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
|
||||
fee = tx_fee(coins.len(), 2, None);
|
||||
amount_with_fee = amount + fee;
|
||||
|
||||
// Here check if we have enough outputs for the amount including fee otherwise
|
||||
|
@ -274,7 +270,7 @@ where
|
|||
max_outputs,
|
||||
selection_strategy_is_use_all,
|
||||
);
|
||||
fee = tx_fee(coins.len(), 2, coins_proof_count(&coins), None);
|
||||
fee = tx_fee(coins.len(), 2, None);
|
||||
total = coins.iter().map(|c| c.value).sum();
|
||||
amount_with_fee = amount + fee;
|
||||
}
|
||||
|
@ -290,11 +286,6 @@ where
|
|||
Ok((parts, coins, change, change_derivation, amount, fee))
|
||||
}
|
||||
|
||||
/// coins proof count
|
||||
pub fn coins_proof_count(coins: &Vec<OutputData>) -> usize {
|
||||
coins.iter().filter(|c| c.merkle_proof.is_some()).count()
|
||||
}
|
||||
|
||||
/// Selects inputs and change for a transaction
|
||||
pub fn inputs_and_change<T, K>(
|
||||
coins: &Vec<OutputData>,
|
||||
|
@ -322,14 +313,8 @@ where
|
|||
for coin in coins {
|
||||
let key_id = wallet.keychain().derive_key_id(coin.n_child)?;
|
||||
if coin.is_coinbase {
|
||||
let block = coin.block.clone();
|
||||
let merkle_proof = coin.merkle_proof.clone();
|
||||
let merkle_proof = merkle_proof.unwrap().merkle_proof();
|
||||
|
||||
parts.push(build::coinbase_input(
|
||||
coin.value,
|
||||
block.unwrap().hash(),
|
||||
merkle_proof,
|
||||
key_id,
|
||||
));
|
||||
} else {
|
||||
|
|
|
@ -162,7 +162,7 @@ where
|
|||
|
||||
debug!(LOGGER, "selected some coins - {}", coins.len());
|
||||
|
||||
let fee = tx_fee(coins.len(), 2, selection::coins_proof_count(&coins), None);
|
||||
let fee = tx_fee(coins.len(), 2, None);
|
||||
let (mut parts, _, _) = selection::inputs_and_change(&coins, wallet, amount, fee)?;
|
||||
|
||||
//TODO: If we end up using this, create change output here
|
||||
|
|
|
@ -65,55 +65,6 @@ where
|
|||
{
|
||||
let height = wallet.get_chain_height()?;
|
||||
refresh_output_state(wallet, height)?;
|
||||
refresh_missing_block_hashes(wallet, height)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO - this might be slow if we have really old outputs that have never been
|
||||
// refreshed
|
||||
fn refresh_missing_block_hashes<T, K>(wallet: &mut T, height: u64) -> Result<(), Error>
|
||||
where
|
||||
T: WalletBackend<K> + WalletClient,
|
||||
K: Keychain,
|
||||
{
|
||||
// build a local map of wallet outputs keyed by commit
|
||||
// and a list of outputs we want to query the node for
|
||||
let wallet_outputs = map_wallet_outputs_missing_block(wallet)?;
|
||||
|
||||
let wallet_output_keys = wallet_outputs.keys().map(|commit| commit.clone()).collect();
|
||||
|
||||
// nothing to do so return (otherwise we hit the api with a monster query...)
|
||||
if wallet_outputs.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
debug!(
|
||||
LOGGER,
|
||||
"Refreshing missing block hashes (and heights) for {} outputs",
|
||||
wallet_outputs.len(),
|
||||
);
|
||||
|
||||
let (api_blocks, api_merkle_proofs) =
|
||||
wallet.get_missing_block_hashes_from_node(height, wallet_output_keys)?;
|
||||
|
||||
// now for each commit, find the output in the wallet and
|
||||
// the corresponding api output (if it exists)
|
||||
// and refresh it in-place in the wallet.
|
||||
// Note: minimizing the time we spend holding the wallet lock.
|
||||
let mut batch = wallet.batch()?;
|
||||
for (commit, id) in wallet_outputs.iter() {
|
||||
if let Some((height, bid)) = api_blocks.get(&commit) {
|
||||
if let Ok(mut output) = batch.get(id) {
|
||||
output.height = *height;
|
||||
output.block = Some(bid.clone());
|
||||
if let Some(merkle_proof) = api_merkle_proofs.get(&commit) {
|
||||
output.merkle_proof = Some(merkle_proof.clone());
|
||||
}
|
||||
batch.save(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
batch.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -139,29 +90,6 @@ where
|
|||
Ok(wallet_outputs)
|
||||
}
|
||||
|
||||
/// As above, but only return unspent outputs with missing block hashes
|
||||
/// and a list of outputs we want to query the node for
|
||||
pub fn map_wallet_outputs_missing_block<T, K>(
|
||||
wallet: &mut T,
|
||||
) -> Result<HashMap<pedersen::Commitment, Identifier>, Error>
|
||||
where
|
||||
T: WalletBackend<K>,
|
||||
K: Keychain,
|
||||
{
|
||||
let mut wallet_outputs: HashMap<pedersen::Commitment, Identifier> = HashMap::new();
|
||||
let keychain = wallet.keychain().clone();
|
||||
let unspents = wallet.iter().filter(|x| {
|
||||
x.root_key_id == keychain.root_key_id()
|
||||
&& x.block.is_none()
|
||||
&& x.status == OutputStatus::Unspent
|
||||
});
|
||||
for out in unspents {
|
||||
let commit = keychain.commit_with_key_index(out.value, out.n_child)?;
|
||||
wallet_outputs.insert(commit, out.key_id.clone());
|
||||
}
|
||||
Ok(wallet_outputs)
|
||||
}
|
||||
|
||||
/// Apply refreshed API output data to the wallet
|
||||
pub fn apply_api_outputs<T, K>(
|
||||
wallet: &mut T,
|
||||
|
@ -329,20 +257,16 @@ where
|
|||
{
|
||||
// Now acquire the wallet lock and write the new output.
|
||||
let mut batch = wallet.batch()?;
|
||||
{
|
||||
batch.save(OutputData {
|
||||
root_key_id: root_key_id.clone(),
|
||||
key_id: key_id.clone(),
|
||||
n_child: derivation,
|
||||
value: reward(block_fees.fees),
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: height,
|
||||
lock_height: lock_height,
|
||||
is_coinbase: true,
|
||||
block: None,
|
||||
merkle_proof: None,
|
||||
})?;
|
||||
}
|
||||
batch.save(OutputData {
|
||||
root_key_id: root_key_id.clone(),
|
||||
key_id: key_id.clone(),
|
||||
n_child: derivation,
|
||||
value: reward(block_fees.fees),
|
||||
status: OutputStatus::Unconfirmed,
|
||||
height: height,
|
||||
lock_height: lock_height,
|
||||
is_coinbase: true,
|
||||
});
|
||||
batch.commit()?;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ use serde_json;
|
|||
use failure::ResultExt;
|
||||
|
||||
use core::core::hash::Hash;
|
||||
use core::core::merkle_proof::MerkleProof;
|
||||
use core::ser;
|
||||
|
||||
use keychain::{Identifier, Keychain};
|
||||
|
@ -150,22 +149,6 @@ pub trait WalletClient {
|
|||
),
|
||||
Error,
|
||||
>;
|
||||
|
||||
/// Get any missing block hashes from node
|
||||
fn get_missing_block_hashes_from_node(
|
||||
&self,
|
||||
height: u64,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<
|
||||
(
|
||||
HashMap<pedersen::Commitment, (u64, BlockIdentifier)>,
|
||||
HashMap<pedersen::Commitment, MerkleProofWrapper>,
|
||||
),
|
||||
Error,
|
||||
>;
|
||||
|
||||
/// create merkle proof for a commit from a node at the current height
|
||||
fn create_merkle_proof(&self, commit: &str) -> Result<MerkleProofWrapper, Error>;
|
||||
}
|
||||
|
||||
/// Information about an output that's being tracked by the wallet. Must be
|
||||
|
@ -190,10 +173,6 @@ pub struct OutputData {
|
|||
pub lock_height: u64,
|
||||
/// Is this a coinbase output? Is it subject to coinbase locktime?
|
||||
pub is_coinbase: bool,
|
||||
/// Hash of the block this output originated from.
|
||||
pub block: Option<BlockIdentifier>,
|
||||
/// Merkle proof
|
||||
pub merkle_proof: Option<MerkleProofWrapper>,
|
||||
}
|
||||
|
||||
impl ser::Writeable for OutputData {
|
||||
|
@ -239,14 +218,6 @@ impl OutputData {
|
|||
return false;
|
||||
} else if self.status == OutputStatus::Unconfirmed && self.is_coinbase {
|
||||
return false;
|
||||
} else if self.is_coinbase && self.block.is_none() {
|
||||
// if we do not have a block hash for coinbase output we cannot spent it
|
||||
// block index got compacted before we refreshed our wallet?
|
||||
return false;
|
||||
} else if self.is_coinbase && self.merkle_proof.is_none() {
|
||||
// if we do not have a Merkle proof for coinbase output we cannot spent it
|
||||
// block index got compacted before we refreshed our wallet?
|
||||
return false;
|
||||
} else if self.lock_height > current_height {
|
||||
return false;
|
||||
} else if self.status == OutputStatus::Unspent
|
||||
|
@ -304,53 +275,6 @@ impl fmt::Display for OutputStatus {
|
|||
}
|
||||
}
|
||||
|
||||
/// Wrapper for a merkle proof
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct MerkleProofWrapper(pub MerkleProof);
|
||||
|
||||
impl MerkleProofWrapper {
|
||||
/// Create
|
||||
pub fn merkle_proof(&self) -> MerkleProof {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::ser::Serialize for MerkleProofWrapper {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.0.to_hex())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::de::Deserialize<'de> for MerkleProofWrapper {
|
||||
fn deserialize<D>(deserializer: D) -> Result<MerkleProofWrapper, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_str(MerkleProofWrapperVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct MerkleProofWrapperVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for MerkleProofWrapperVisitor {
|
||||
type Value = MerkleProofWrapper;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a merkle proof")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let merkle_proof = MerkleProof::from_hex(s).unwrap();
|
||||
Ok(MerkleProofWrapper(merkle_proof))
|
||||
}
|
||||
}
|
||||
|
||||
/// Block Identifier
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct BlockIdentifier(pub Hash);
|
||||
|
|
|
@ -251,28 +251,4 @@ impl<K> WalletClient for LMDBBackend<K> {
|
|||
.context(ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Get any missing block hashes from node
|
||||
fn get_missing_block_hashes_from_node(
|
||||
&self,
|
||||
height: u64,
|
||||
wallet_outputs: Vec<pedersen::Commitment>,
|
||||
) -> Result<
|
||||
(
|
||||
HashMap<pedersen::Commitment, (u64, BlockIdentifier)>,
|
||||
HashMap<pedersen::Commitment, MerkleProofWrapper>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let res =
|
||||
client::get_missing_block_hashes_from_node(self.node_url(), height, wallet_outputs)
|
||||
.context(ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// retrieve merkle proof for a commit from a node
|
||||
fn create_merkle_proof(&self, commit: &str) -> Result<MerkleProofWrapper, Error> {
|
||||
let res = client::create_merkle_proof(self.node_url(), commit).context(ErrorKind::Node)?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ use keychain::ExtKeychain;
|
|||
use wallet::WalletConfig;
|
||||
use wallet::file_wallet::FileWallet;
|
||||
use wallet::libwallet::internal::updater;
|
||||
use wallet::libwallet::types::{BlockFees, BlockIdentifier, MerkleProofWrapper, OutputStatus,
|
||||
use wallet::libwallet::types::{BlockFees, BlockIdentifier, OutputStatus,
|
||||
WalletBackend};
|
||||
use wallet::libwallet::{Error, ErrorKind};
|
||||
|
||||
|
@ -176,13 +176,7 @@ where
|
|||
}
|
||||
};
|
||||
add_block_with_reward(chain, txs, coinbase_tx.clone());
|
||||
// build merkle proof and block identifier and save in wallet
|
||||
let output_id = OutputIdentifier::from_output(&coinbase_tx.0.clone());
|
||||
let m_proof = chain.get_merkle_proof(&output_id, &chain.head_header().unwrap());
|
||||
let block_id = Some(BlockIdentifier(chain.head_header().unwrap().hash()));
|
||||
let mut output = wallet.get(&fees.key_id.unwrap()).unwrap();
|
||||
output.block = block_id;
|
||||
output.merkle_proof = Some(MerkleProofWrapper(m_proof.unwrap()));
|
||||
let output = wallet.get(&fees.key_id.unwrap()).unwrap();
|
||||
let mut batch = wallet.batch().unwrap();
|
||||
batch.save(output).unwrap();
|
||||
batch.commit().unwrap();
|
||||
|
|
Loading…
Reference in a new issue