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
......@@ -141,7 +141,7 @@ namespace Breeze.Wallet
/// <param name="allowUnconfirmed">Whether or not we allow this transaction to rely on unconfirmed outputs.</param>
/// <returns></returns>
(string hex, uint256 transactionId, Money fee) BuildTransaction(string walletName, string accountName, CoinType coinType, string password, string destinationAddress, Money amount, string feeType, bool allowUnconfirmed);
/// <summary>
/// Sends a transaction to the network.
/// </summary>
......@@ -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.
......@@ -188,4 +188,4 @@ namespace Breeze.Wallet
/// <param name="height">The height of the last block synced.</param>
void UpdateLastBlockSyncedHeight(int height);
}
}
}
\ No newline at end of file
......@@ -31,7 +31,7 @@ namespace Breeze.Wallet
[JsonProperty(PropertyName = "chainCode")]
[JsonConverter(typeof(ByteArrayConverter))]
public byte[] ChainCode { get; set; }
/// <summary>
/// The network this wallet is for.
/// </summary>
......@@ -77,7 +77,7 @@ namespace Breeze.Wallet
{
List<TransactionData> result = new List<TransactionData>();
var accounts = this.GetAccountsByCoinType(coinType).ToList();
foreach (var address in accounts.SelectMany(a => a.ExternalAddresses).Concat(accounts.SelectMany(a => a.InternalAddresses)))
{
result.AddRange(address.Transactions);
......@@ -91,12 +91,12 @@ namespace Breeze.Wallet
/// <param name="coinType">Type of the coin.</param>
/// <returns></returns>
public IEnumerable<Script> GetAllPubKeysByCoinType(CoinType coinType)
{
{
var accounts = this.GetAccountsByCoinType(coinType).ToList();
foreach (var address in accounts.SelectMany(a => a.ExternalAddresses).Concat(accounts.SelectMany(a => a.InternalAddresses)))
{
yield return address.ScriptPubKey;
}
}
}
}
......@@ -116,7 +116,7 @@ namespace Breeze.Wallet
/// </summary>
[JsonProperty(PropertyName = "lastBlockSyncedHeight", NullValueHandling = NullValueHandling.Ignore)]
public int? LastBlockSyncedHeight { get; set; }
/// <summary>
/// The accounts used in the wallet.
/// </summary>
......@@ -155,7 +155,7 @@ namespace Breeze.Wallet
throw new Exception($"No account with name {accountName} could be found.");
}
return account;
}
}
}
/// <summary>
......@@ -315,7 +315,7 @@ namespace Breeze.Wallet
/// <returns></returns>
public IEnumerable<TransactionData> GetSpendableTransactions()
{
var addresses = this.ExternalAddresses.Concat(this.InternalAddresses);
var addresses = this.ExternalAddresses.Concat(this.InternalAddresses);
return addresses.SelectMany(a => a.Transactions.Where(t => t.SpentInTransaction == null && t.Amount > Money.Zero));
}
......@@ -344,7 +344,7 @@ namespace Breeze.Wallet
/// </summary>
[JsonProperty(PropertyName = "index")]
public int Index { get; set; }
/// <summary>
/// The script pub key for this address.
/// </summary>
......@@ -355,7 +355,7 @@ namespace Breeze.Wallet
/// <summary>
/// The Base58 representation of this address.
/// </summary>
[JsonProperty(PropertyName = "address")]
[JsonProperty(PropertyName = "address")]
public string Address { get; set; }
/// <summary>
......@@ -383,7 +383,7 @@ namespace Breeze.Wallet
/// <c>true</c> if it is a change address; otherwise, <c>false</c>.
/// </returns>
public bool IsChangeAddress()
{
{
return int.Parse(this.HdPath.Split('/')[4]) == 1;
}
}
......@@ -431,7 +431,7 @@ namespace Breeze.Wallet
/// </summary>
[JsonProperty(PropertyName = "blockHeight", NullValueHandling = NullValueHandling.Ignore)]
public int? BlockHeight { get; set; }
/// <summary>
/// Gets or sets the creation time.
/// </summary>
......@@ -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(block.Header.Time);
}
// add the Merkle proof now that the transaction is confirmed in a block
if (!isSpendingTransaction && foundTransaction.MerkleProof == null)
{
foundTransaction.CreationTime = DateTimeOffset.FromUnixTimeSeconds(blockTime.Value);
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)
......@@ -835,4 +859,4 @@ namespace Breeze.Wallet
this.TransactionHash = transactionHash;
}
}
}
}
\ No newline at end of file
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