Commit 99adb77d authored by Dan Gershony's avatar Dan Gershony Committed by GitHub

Merge pull request #80 from bokobza/master

Merkle proofs
parents 662503f8 8b02cbba
......@@ -161,8 +161,8 @@ namespace Breeze.Wallet
/// </summary>
/// <param name="transaction">The transaction.</param>
/// <param name="blockHeight">The height of the block this transaction came from. Null if it was not a transaction included in a block.</param>
/// <param name="blockTime">The block time.</param>
void ProcessTransaction(Transaction transaction, int? blockHeight = null, uint? blockTime = null);
/// <param name="block">The block in which this transaction was included.</param>
void ProcessTransaction(Transaction transaction, int? blockHeight = null, Block block = null);
/// <summary>
/// Saves the wallet into the file system.
......
......@@ -439,6 +439,13 @@ namespace Breeze.Wallet
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset CreationTime { get; set; }
/// <summary>
/// Gets or sets the Merkle proof for this transaction.
/// </summary>
[JsonProperty(PropertyName = "merkleProof", NullValueHandling = NullValueHandling.Ignore)]
public MerkleProof MerkleProof { get; set; }
/// <summary>
/// Determines whether this transaction is confirmed.
/// </summary>
......@@ -473,4 +480,23 @@ namespace Breeze.Wallet
[JsonConverter(typeof(MoneyJsonConverter))]
public Money Amount { get; set; }
}
/// <summary>
/// An object representing a Merkle proof
/// </summary>
public class MerkleProof
{
/// <summary>
/// Gets or sets the merkle root.
/// </summary>
[JsonProperty(PropertyName = "merkleRoot")]
[JsonConverter(typeof(UInt256JsonConverter))]
public uint256 MerkleRoot { get; set; }
/// <summary>
/// Gets or sets the merkle path.
/// </summary>
[JsonProperty(PropertyName = "merklePath", ItemConverterType = typeof(UInt256JsonConverter))]
public ICollection<uint256> MerklePath { get; set; }
}
}
\ No newline at end of file
......@@ -451,7 +451,7 @@ namespace Breeze.Wallet
foreach (Transaction transaction in block.Transactions)
{
this.ProcessTransaction(transaction, height, block.Header.Time);
this.ProcessTransaction(transaction, height, block);
}
// update the wallets with the last processed block height
......@@ -459,7 +459,7 @@ namespace Breeze.Wallet
}
/// <inheritdoc />
public void ProcessTransaction(Transaction transaction, int? blockHeight = null, uint? blockTime = null)
public void ProcessTransaction(Transaction transaction, int? blockHeight = null, Block block = null)
{
Console.WriteLine($"transaction notification: tx hash {transaction.GetHash()}, coin type: {this.coinType}");
......@@ -470,7 +470,7 @@ namespace Breeze.Wallet
var utxo = transaction.Outputs.SingleOrDefault(o => pubKey == o.ScriptPubKey);
if (utxo != null)
{
AddTransactionToWallet(transaction.GetHash(), transaction.Time, transaction.Outputs.IndexOf(utxo), utxo.Value, pubKey, blockHeight, blockTime);
AddTransactionToWallet(transaction.GetHash(), transaction.Time, transaction.Outputs.IndexOf(utxo), utxo.Value, pubKey, blockHeight, block);
}
}
......@@ -486,7 +486,7 @@ namespace Breeze.Wallet
// We first include the keys we don't hold and then we include the keys we do hold but that are for receiving addresses (which would mean the user paid itself).
IEnumerable<TxOut> paidoutto = transaction.Outputs.Where(o => !this.keysLookup.Keys.Contains(o.ScriptPubKey) || (this.keysLookup.ContainsKey(o.ScriptPubKey) && !this.keysLookup[o.ScriptPubKey].IsChangeAddress()));
AddTransactionToWallet(transaction.GetHash(), transaction.Time, null, -tTx.Amount, keyToSpend, blockHeight, blockTime, tTx.Id, tTx.Index, paidoutto);
AddTransactionToWallet(transaction.GetHash(), transaction.Time, null, -tTx.Amount, keyToSpend, blockHeight, block, tTx.Id, tTx.Index, paidoutto);
}
}
......@@ -499,14 +499,15 @@ namespace Breeze.Wallet
/// <param name="amount">The amount.</param>
/// <param name="script">The script.</param>
/// <param name="blockHeight">Height of the block.</param>
/// <param name="blockTime">The block time.</param>
/// <param name="block">The block containing the transaction to add.</param>
/// <param name="spendingTransactionId">The id of the transaction containing the output being spent, if this is a spending transaction.</param>
/// <param name="spendingTransactionIndex">The index of the output in the transaction being referenced, if this is a spending transaction.</param>
private void AddTransactionToWallet(uint256 transactionHash, uint time, int? index, Money amount, Script script, int? blockHeight = null, uint? blockTime = null, uint256 spendingTransactionId = null, int? spendingTransactionIndex = null, IEnumerable<TxOut> paidToOutputs = null)
private void AddTransactionToWallet(uint256 transactionHash, uint time, int? index, Money amount, Script script, int? blockHeight = null, Block block = null, uint256 spendingTransactionId = null, int? spendingTransactionIndex = null, IEnumerable<TxOut> paidToOutputs = null)
{
// get the collection of transactions to add to.
this.keysLookup.TryGetValue(script, out HdAddress address);
var isSpendingTransaction = paidToOutputs != null && paidToOutputs.Any();
var trans = address.Transactions;
// if it's the first time we see this transaction
......@@ -517,13 +518,18 @@ namespace Breeze.Wallet
Amount = amount,
BlockHeight = blockHeight,
Id = transactionHash,
CreationTime = DateTimeOffset.FromUnixTimeSeconds(blockTime ?? time),
CreationTime = DateTimeOffset.FromUnixTimeSeconds(block?.Header.Time ?? time),
Index = index
};
trans.Add(newTransaction);
// add the Merkle proof to the (non-spending) transaction
if (block != null && !isSpendingTransaction)
{
newTransaction.MerkleProof = this.CreateMerkleProof(block, transactionHash);
}
// if this is a spending transaction, keep a record of the payments made out to other scripts.
if (paidToOutputs != null && paidToOutputs.Any())
if (isSpendingTransaction)
{
List<PaymentDetails> payments = new List<PaymentDetails>();
foreach (var paidToOutput in paidToOutputs)
......@@ -537,17 +543,18 @@ namespace Breeze.Wallet
}
newTransaction.Payments = payments;
}
// if this is a spending transaction, mark the spent transaction as such
if (spendingTransactionId != null)
{
// mark the transaction spent by this transaction as such
var transactions = this.keysLookup.Values.SelectMany(v => v.Transactions).Where(t => t.Id == spendingTransactionId);
if (transactions.Any())
{
transactions.Single(t => t.Index == spendingTransactionIndex).SpentInTransaction = transactionHash;
var spentTransaction = transactions.Single(t => t.Index == spendingTransactionIndex);
spentTransaction.SpentInTransaction = transactionHash;
spentTransaction.MerkleProof = null;
}
}
trans.Add(newTransaction);
}
else if (trans.Any(t => t.Id == transactionHash)) // if this is an unconfirmed transaction now received in a block
{
......@@ -560,9 +567,15 @@ namespace Breeze.Wallet
}
// update the block time
if (blockTime != null)
if (block != null)
{
foundTransaction.CreationTime = DateTimeOffset.FromUnixTimeSeconds(blockTime.Value);
foundTransaction.CreationTime = DateTimeOffset.FromUnixTimeSeconds(block.Header.Time);
}
// add the Merkle proof now that the transaction is confirmed in a block
if (!isSpendingTransaction && foundTransaction.MerkleProof == null)
{
foundTransaction.MerkleProof = this.CreateMerkleProof(block, transactionHash);
}
}
......@@ -570,6 +583,17 @@ namespace Breeze.Wallet
this.TransactionFound?.Invoke(this, new TransactionFoundEventArgs(script, transactionHash));
}
private MerkleProof CreateMerkleProof(Block block, uint256 transactionHash)
{
MerkleBlock merkleBlock = new MerkleBlock(block, new[] { transactionHash });
return new MerkleProof
{
MerkleRoot = block.Header.HashMerkleRoot,
MerklePath = merkleBlock.PartialMerkleTree.Hashes
};
}
private void OnTransactionFound(object sender, TransactionFoundEventArgs a)
{
foreach (Wallet wallet in this.Wallets)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment