Commit 36cf64fb authored by Dan Gershony's avatar Dan Gershony Committed by GitHub

Merge pull request #52 from bokobza/feature/build-transaction

Sending funds
parents 86d060a2 3c3778b8
......@@ -148,9 +148,17 @@ POST /wallet/send-transaction - Attempts to send a transaction
### Parameters
```
{
"password": "password"
"password": "123456",
"folderPath": "Wallets", // optional, if the folder path is not the default one
"name": "myWallet"
}
```
### Response
```
200 (OK)
```
## POST /wallet/recover - Recovers the wallet
### Parameters
```
......@@ -158,11 +166,16 @@ POST /wallet/send-transaction - Attempts to send a transaction
"network": "main", // "main" or "testnet"
"password": "password",
"mnemonic": "foo bar buz",
"creationTime": "2017-02-03" // DateTimeOffset.ParseExact("1998-01-01", "yyyy-MM-dd", CultureInfo.InvariantCulture), utc time
"name": "testwallet-recovered",
"folderPath": "Wallets", // optional, if the folder path is not the default one
"creationTime": "2017-02-25 16:20:33" // date from which to start looking for transactions
}
```
### Response
Cannot check if the password is good or not. If the password is wrong it'll recover a wallet with the wrong password.
```
200 (OK)
```
## DELETE /wallet - Deletes the wallet
Works as expected.
......
......@@ -16,12 +16,12 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
<PackageReference Include="NStratis" Version="3.0.2.17" />
<PackageReference Include="NStratis" Version="3.0.2.23" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0-beta5-build1225" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
<PackageReference Include="xunit" Version="2.2.0-beta5-build3474" />
<PackageReference Include="Microsoft.DotNet.InternalAbstractions" Version="1.0.0" />
<PackageReference Include="Moq" Version="4.7.8" />
<PackageReference Include="Moq" Version="4.7.10" />
</ItemGroup>
</Project>
......@@ -66,12 +66,8 @@ namespace Breeze.Api.Tests
// Assert
mockWalletWrapper.VerifyAll();
var viewResult = Assert.IsType<JsonResult>(result);
Assert.NotNull(viewResult.Value);
Assert.IsType<WalletModel>(viewResult.Value);
var model = viewResult.Value as WalletModel;
Assert.Equal("Main", model.Network);
var viewResult = Assert.IsType<OkResult>(result);
Assert.Equal(200, viewResult.StatusCode);
}
[Fact]
......@@ -97,12 +93,8 @@ namespace Breeze.Api.Tests
// Assert
mockWalletWrapper.VerifyAll();
var viewResult = Assert.IsType<JsonResult>(result);
Assert.NotNull(viewResult.Value);
Assert.IsType<WalletModel>(viewResult.Value);
var model = viewResult.Value as WalletModel;
Assert.Equal("Main", model.Network);
var viewResult = Assert.IsType<OkResult>(result);
Assert.Equal(200, viewResult.StatusCode);
}
[Fact]
......
......@@ -2,7 +2,7 @@
"variables": [],
"info": {
"name": "Wallet",
"_postman_id": "57013f2c-02dc-df32-41e9-6e4aaa14ad5e",
"_postman_id": "b5720ab4-24a5-6957-0ea6-766a9cbaf488",
"description": "Requests relating to operations on the wallet",
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
},
......@@ -21,7 +21,7 @@
],
"body": {
"mode": "raw",
"raw": "{ \n\t\"password\": \"123456\",\n\t\"network\": \"Main\",\n\t\"folderPath\": \"Wallets\",\n\t\"name\": \"myFirstWallet\"\n}"
"raw": "{ \n\t\"password\": \"123456\",\n\t\"network\": \"testnet\",\n\t\"name\": \"testwallet\"\n}"
},
"description": ""
},
......@@ -161,7 +161,7 @@
],
"body": {
"mode": "raw",
"raw": "{\r\n \"password\": \"password\",\r\n \"address\": \"1FYp9uguYCz7DgSF9jTWDeZF8kdRKQTXPg\",\r\n \"amount\": \"0.12\",\r\n \"feeType\": \"low\",\r\n \"allowUnconfirmed\": \"true\"\r\n}"
"raw": "{\r\n\t\"walletName\": \"testwallet\",\r\n\t\"accountName\": \"account 0\",\r\n\t\"coinType\": 1,\r\n \"password\": \"password\",\r\n \"destinationAddress\": \"1FYp9uguYCz7DgSF9jTWDeZF8kdRKQTXPg\",\r\n \"amount\": \"0.12\",\r\n \"feeType\": \"low\",\r\n \"allowUnconfirmed\": \"true\"\r\n}"
},
"description": ""
},
......
......@@ -78,14 +78,9 @@ namespace Breeze.Wallet.Controllers
{
// get the wallet folder
DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath);
Wallet wallet = this.walletManager.LoadWallet(request.Password, walletFolder.FullName, request.Name);
return this.Json(new WalletModel
{
Network = wallet.Network.Name,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = wallet.WalletFilePath
});
return this.Ok();
}
catch (FileNotFoundException e)
{
......@@ -122,18 +117,12 @@ namespace Breeze.Wallet.Controllers
{
// get the wallet folder
DirectoryInfo walletFolder = GetWalletFolder(request.FolderPath);
Wallet wallet = this.walletManager.RecoverWallet(request.Password, walletFolder.FullName, request.Name, request.Network, request.Mnemonic, null, request.CreationDate);
// start syncing the wallet from the creation date
this.tracker.SyncFrom(request.CreationDate);
return this.Json(new WalletModel
{
Network = wallet.Network.Name,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName = wallet.WalletFilePath
});
return this.Ok();
}
catch (InvalidOperationException e)
{
......@@ -287,12 +276,11 @@ namespace Breeze.Wallet.Controllers
try
{
var transaction = this.walletManager.BuildTransaction(request.WalletName, request.AccountName, request.CoinType, request.Password, request.DestinationAddress, request.Amount, request.FeeType, request.AllowUnconfirmed);
var fee = transaction.TotalOut - request.Amount;
var transactionResult = this.walletManager.BuildTransaction(request.WalletName, request.AccountName, request.CoinType, request.Password, request.DestinationAddress, request.Amount, request.FeeType, request.AllowUnconfirmed);
var model = new WalletBuildTransactionModel
{
Hex = transaction.ToHex(),
Fee = fee
Hex = transactionResult.hex,
Fee = transactionResult.fee
};
return this.Json(model);
}
......
......@@ -135,25 +135,28 @@ namespace Breeze.Wallet
/// <param name="feeType">The type of fee to be included.</param>
/// <param name="allowUnconfirmed">Whether or not we allow this transaction to rely on unconfirmed outputs.</param>
/// <returns></returns>
NBitcoin.Transaction BuildTransaction(string walletName, string accountName, CoinType coinType, string password, string destinationAddress, Money amount, string feeType, bool allowUnconfirmed);
(string hex, 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>
/// <param name="transactionHex">The hex of the transaction.</param>
/// <returns></returns>
bool SendTransaction(string transactionHex);
/// <summary>
/// Processes a block received from the network.
/// </summary>
/// <param name="coinType">The type of coin this block relates to.</param>
/// <param name="height">The height of the block in the blockchain.</param>
/// <param name="block">The block.</param>
void ProcessBlock(CoinType coinType, int height, Block block);
void ProcessBlock(int height, Block block);
/// <summary>
/// Processes a transaction received from the network.
/// </summary>
/// <param name="coinType">The type of coin this transaction relates to.</param>
/// <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(CoinType coinType, NBitcoin.Transaction transaction, int? blockHeight = null, uint? blockTime = null);
void ProcessTransaction(Transaction transaction, int? blockHeight = null, uint? blockTime = null);
}
}
......@@ -9,13 +9,11 @@ namespace Breeze.Wallet.Notifications
public class BlockObserver : SignalObserver<Block>
{
private readonly ConcurrentChain chain;
private readonly CoinType coinType;
private readonly IWalletManager walletManager;
public BlockObserver(ConcurrentChain chain, CoinType coinType, IWalletManager walletManager)
public BlockObserver(ConcurrentChain chain, IWalletManager walletManager)
{
this.chain = chain;
this.coinType = coinType;
this.walletManager = walletManager;
}
......@@ -28,7 +26,7 @@ namespace Breeze.Wallet.Notifications
var hash = block.Header.GetHash();
var height = this.chain.GetBlock(hash).Height;
this.walletManager.ProcessBlock(this.coinType, height, block);
this.walletManager.ProcessBlock(height, block);
}
}
}
......@@ -8,13 +8,10 @@ namespace Breeze.Wallet.Notifications
/// </summary>
public class TransactionObserver : SignalObserver<Transaction>
{
private readonly CoinType coinType;
private readonly IWalletManager walletManager;
public TransactionObserver(CoinType coinType, IWalletManager walletManager)
public TransactionObserver(IWalletManager walletManager)
{
this.coinType = coinType;
this.walletManager = walletManager;
}
......@@ -24,7 +21,7 @@ namespace Breeze.Wallet.Notifications
/// <param name="transaction">The new transaction</param>
protected override void OnNextCore(Transaction transaction)
{
this.walletManager.ProcessTransaction(this.coinType, transaction);
this.walletManager.ProcessTransaction(transaction);
}
}
}
......@@ -38,9 +38,9 @@ namespace Breeze.Wallet
await this.WaitForChainDownloadAsync();
// subscribe to receiving blocks and transactions
BlockSubscriber sub = new BlockSubscriber(this.signals.Blocks, new BlockObserver(this.chain, this.coinType, this.walletManager));
BlockSubscriber sub = new BlockSubscriber(this.signals.Blocks, new BlockObserver(this.chain, this.walletManager));
sub.Subscribe();
TransactionSubscriber txSub = new TransactionSubscriber(this.signals.Transactions, new TransactionObserver(this.coinType, this.walletManager));
TransactionSubscriber txSub = new TransactionSubscriber(this.signals.Transactions, new TransactionObserver(this.walletManager));
txSub.Subscribe();
// start syncing blocks
......
......@@ -84,6 +84,20 @@ namespace Breeze.Wallet
}
return result;
}
/// <summary>
/// Gets all the pub keys conatined in this wallet.
/// </summary>
/// <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;
}
}
}
/// <summary>
......@@ -107,7 +121,7 @@ namespace Breeze.Wallet
/// The accounts used in the wallet.
/// </summary>
[JsonProperty(PropertyName = "accounts")]
public IEnumerable<HdAccount> Accounts { get; set; }
public ICollection<HdAccount> Accounts { get; set; }
/// <summary>
/// Gets the first account that contains no transaction.
......@@ -142,6 +156,9 @@ namespace Breeze.Wallet
}
return account;
}
}
/// <summary>
......@@ -210,13 +227,13 @@ namespace Breeze.Wallet
/// The list of external addresses, typically used for receiving money.
/// </summary>
[JsonProperty(PropertyName = "externalAddresses")]
public IEnumerable<HdAddress> ExternalAddresses { get; set; }
public ICollection<HdAddress> ExternalAddresses { get; set; }
/// <summary>
/// The list of internal addresses, typically used to receive change.
/// </summary>
[JsonProperty(PropertyName = "internalAddresses")]
public IEnumerable<HdAddress> InternalAddresses { get; set; }
public ICollection<HdAddress> InternalAddresses { get; set; }
/// <summary>
/// Gets the type of coin this account is for.
......@@ -357,7 +374,7 @@ namespace Breeze.Wallet
/// A list of transactions involving this address.
/// </summary>
[JsonProperty(PropertyName = "transactions")]
public IEnumerable<TransactionData> Transactions { get; set; }
public ICollection<TransactionData> Transactions { get; set; }
}
/// <summary>
......
......@@ -7,7 +7,9 @@ using Breeze.Wallet.Helpers;
using Breeze.Wallet.Models;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitcoin.Protocol;
using Newtonsoft.Json;
using Stratis.Bitcoin.Connection;
using Transaction = NBitcoin.Transaction;
namespace Breeze.Wallet
......@@ -19,10 +21,6 @@ namespace Breeze.Wallet
{
public List<Wallet> Wallets { get; }
public HashSet<Script> PubKeys { get; set; }
public HashSet<TransactionDetails> TrackedTransactions { get; }
private const int UnusedAddressesBuffer = 20;
private const int WalletRecoveryAccountsCount = 3;
......@@ -31,12 +29,16 @@ namespace Breeze.Wallet
private readonly CoinType coinType;
private readonly ConnectionManager connectionManager;
private Dictionary<Script, ICollection<TransactionData>> keysLookup;
/// <summary>
/// Occurs when a transaction is found.
/// </summary>
public event EventHandler<TransactionFoundEventArgs> TransactionFound;
public WalletManager(ConcurrentChain chain, Network netwrok)
public WalletManager(ConnectionManager connectionManager, Network netwrok)
{
this.Wallets = new List<Wallet>();
......@@ -46,12 +48,13 @@ namespace Breeze.Wallet
this.Load(this.DeserializeWallet(path));
}
this.connectionManager = connectionManager;
this.coinType = (CoinType)netwrok.Consensus.CoinType;
// load data in memory for faster lookups
// TODO get the coin type from somewhere else
this.PubKeys = this.LoadKeys(this.coinType);
this.TrackedTransactions = this.LoadTransactions(this.coinType);
this.LoadKeysLookup();
// register events
this.TransactionFound += this.OnTransactionFound;
}
......@@ -84,7 +87,7 @@ namespace Breeze.Wallet
// save the changes to the file and add addresses to be tracked
this.SaveToFile(wallet);
this.PubKeys = this.LoadKeys(this.coinType);
this.LoadKeysLookup();
this.Load(wallet);
return mnemonic;
......@@ -129,7 +132,7 @@ namespace Breeze.Wallet
// save the changes to the file and add addresses to be tracked
this.SaveToFile(wallet);
this.PubKeys = this.LoadKeys(this.coinType);
this.LoadKeysLookup();
this.Load(wallet);
return wallet;
......@@ -231,7 +234,7 @@ namespace Breeze.Wallet
this.SaveToFile(wallet);
// adds the address to the list of tracked addresses
this.PubKeys = this.LoadKeys(coinType);
this.LoadKeysLookup();
return account.GetFirstUnusedReceivingAddress().Address;
}
......@@ -284,14 +287,14 @@ namespace Breeze.Wallet
BitcoinPubKeyAddress address = this.GenerateAddress(account.ExtendedPubKey, i, isChange, network);
// add address details
addresses = addresses.Concat(new[] {new HdAddress
addresses.Add(new HdAddress
{
Index = i,
HdPath = CreateBip44Path(account.GetCoinType(), account.Index, i, isChange),
ScriptPubKey = address.ScriptPubKey,
Address = address.ToString(),
Transactions = new List<TransactionData>()
}});
});
addressesCreated.Add(address.ToString());
}
......@@ -322,7 +325,7 @@ namespace Breeze.Wallet
}
/// <inheritdoc />
public Transaction BuildTransaction(string walletName, string accountName, CoinType coinType, string password, string destinationAddress, Money amount, string feeType, bool allowUnconfirmed)
public (string hex, Money fee) BuildTransaction(string walletName, string accountName, CoinType coinType, string password, string destinationAddress, Money amount, string feeType, bool allowUnconfirmed)
{
if (amount == Money.Zero)
{
......@@ -385,7 +388,7 @@ namespace Breeze.Wallet
throw new Exception("Could not build transaction, please make sure you entered the correct data.");
}
return tx;
return (tx.ToHex(), calculationResult.fee);
}
/// <summary>
......@@ -411,25 +414,36 @@ namespace Breeze.Wallet
return (transactionsToUse, fee);
}
/// <inheritdoc />
public bool SendTransaction(string transactionHex)
{
throw new System.NotImplementedException();
// TODO move this to a behavior on the full node
// parse transaction
Transaction transaction = Transaction.Parse(transactionHex);
TxPayload payload = new TxPayload(transaction);
foreach (var node in this.connectionManager.ConnectedNodes)
{
node.SendMessage(payload);
}
return true;
}
/// <inheritdoc />
public void ProcessBlock(CoinType coinType, int height, Block block)
public void ProcessBlock(int height, Block block)
{
Console.WriteLine($"block notification: height: {height}, block hash: {block.Header.GetHash()}, coin type: {coinType}");
Console.WriteLine($"block notification: height: {height}, block hash: {block.Header.GetHash()}, coin type: {this.coinType}");
foreach (Transaction transaction in block.Transactions)
{
this.ProcessTransaction(coinType, transaction, height, block.Header.Time);
this.ProcessTransaction(transaction, height, block.Header.Time);
}
// update the wallets with the last processed block height
foreach (var wallet in this.Wallets)
{
foreach (var accountRoot in wallet.AccountsRoot.Where(a => a.CoinType == coinType))
foreach (var accountRoot in wallet.AccountsRoot.Where(a => a.CoinType == this.coinType))
{
accountRoot.LastBlockSyncedHeight = height;
}
......@@ -437,37 +451,35 @@ namespace Breeze.Wallet
}
/// <inheritdoc />
public void ProcessTransaction(CoinType coinType, Transaction transaction, int? blockHeight = null, uint? blockTime = null)
public void ProcessTransaction(Transaction transaction, int? blockHeight = null, uint? blockTime = null)
{
Console.WriteLine($"transaction notification: tx hash {transaction.GetHash()}, coin type: {coinType}");
Console.WriteLine($"transaction notification: tx hash {transaction.GetHash()}, coin type: {this.coinType}");
foreach (var pubKey in this.PubKeys)
// check the outputs
foreach (var pubKey in this.keysLookup.Keys)
{
// check if the outputs contain one of our addresses
var utxo = transaction.Outputs.SingleOrDefault(o => pubKey == o.ScriptPubKey);
if (utxo != null)
{
AddTransactionToWallet(coinType, 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, blockTime);
}
}
// if the inputs have a reference to a transaction containing one of our scripts
foreach (TxIn input in transaction.Inputs.Where(txIn => this.TrackedTransactions.Any(trackedTx => trackedTx.Hash == txIn.PrevOut.Hash)))
// check the inputs - include those that have a reference to a transaction containing one of our scripts and the same index
foreach (TxIn input in transaction.Inputs.Where(txIn => this.keysLookup.Values.SelectMany(v => v).Any(trackedTx => trackedTx.Id == txIn.PrevOut.Hash && trackedTx.Index == txIn.PrevOut.N)))
{
TransactionDetails tTx = this.TrackedTransactions.Single(trackedTx => trackedTx.Hash == input.PrevOut.Hash);
TransactionData tTx = this.keysLookup.Values.SelectMany(v => v).Single(trackedTx => trackedTx.Id == input.PrevOut.Hash && trackedTx.Index == input.PrevOut.N);
// compare the index of the output in its original transaction and the index references in the input
if (input.PrevOut.N == tTx.Index)
{
AddTransactionToWallet(coinType, transaction.GetHash(), transaction.Time, null, -tTx.Amount, pubKey, blockHeight, blockTime, tTx.Hash, tTx.Index);
}
}
// find the script this input references
var keyToSpend = this.keysLookup.Single(v => v.Value.Contains(tTx)).Key;
AddTransactionToWallet(transaction.GetHash(), transaction.Time, null, -tTx.Amount, keyToSpend, blockHeight, blockTime, tTx.Id, tTx.Index);
}
}
/// <summary>
/// Adds the transaction to the wallet.
/// </summary>
/// <param name="coinType">Type of the coin.</param>
/// <param name="transactionHash">The transaction hash.</param>
/// <param name="time">The time.</param>
/// <param name="index">The index.</param>
......@@ -477,29 +489,10 @@ namespace Breeze.Wallet
/// <param name="blockTime">The block time.</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(CoinType coinType, uint256 transactionHash, uint time, int? index, Money amount, Script script, int? blockHeight = null, uint? blockTime = null, uint256 spendingTransactionId = null, int? spendingTransactionIndex = null)
{
// selects all the transactions we already have in the wallet
var txs = this.Wallets.
SelectMany(w => w.AccountsRoot.Where(a => a.CoinType == coinType)).
SelectMany(a => a.Accounts).
SelectMany(a => a.ExternalAddresses).
SelectMany(t => t.Transactions);
// add this transaction if it is not in the list
if (txs.All(t => t.Id != transactionHash))
{
foreach (var wallet in this.Wallets)
{
foreach (var accountRoot in wallet.AccountsRoot.Where(a => a.CoinType == coinType))
{
foreach (var account in accountRoot.Accounts)
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)
{
foreach (var address in account.ExternalAddresses.Where(a => a.ScriptPubKey == script))
{
address.Transactions = address.Transactions.Concat(new[]
{
new TransactionData
this.keysLookup.TryGetValue(script, out ICollection<TransactionData> trans);
trans.Add(new TransactionData
{
Amount = amount,
BlockHeight = blockHeight,
......@@ -507,55 +500,55 @@ namespace Breeze.Wallet
Id = transactionHash,
CreationTime = DateTimeOffset.FromUnixTimeMilliseconds(blockTime ?? time),
Index = index
}
});
// notify a transaction has been found
this.TransactionFound?.Invoke(this, new TransactionFoundEventArgs(wallet, accountRoot.CoinType, account, address, false));
}
// if this is a spending transaction, mark the spent transaction as such
if (spendingTransactionId != null)
{
var transactions = account.GetTransactionsById(spendingTransactionId);
var transactions = this.keysLookup.Values.SelectMany(v => v).Where(t => t.Id == spendingTransactionId);
if (transactions.Any())
{
transactions.Single(t => t.Index == spendingTransactionIndex).SpentInTransaction = transactionHash;
}
}
}
}
// notify a transaction has been found
this.TransactionFound?.Invoke(this, new TransactionFoundEventArgs(script, transactionHash));
}
this.TrackedTransactions.Add(new TransactionDetails
private void OnTransactionFound(object sender, TransactionFoundEventArgs a)
{
Hash = transactionHash,
Index = index,
Amount = amount
});
foreach (Wallet wallet in this.Wallets)
{
foreach (var account in wallet.GetAccountsByCoinType(this.coinType))
{
bool isChange;
if (account.ExternalAddresses.Any(address => address.ScriptPubKey == a.Script))
{
isChange = false;
}
else if (account.InternalAddresses.Any(address => address.ScriptPubKey == a.Script))
{
isChange = true;
}
private void OnTransactionFound(object sender, TransactionFoundEventArgs a)
else
{
Console.WriteLine("event raised");
var wallet = this.Wallets.Single(w => w.Name == a.WalletName);
var accountsRoot = wallet.AccountsRoot.Single(ar => ar.CoinType == a.CoinType);
var account = accountsRoot.Accounts.Single(acc => acc.Name == a.AccountName);
continue;
}
// calculate how many accounts to add to keep a buffer of 20 unused addresses
int lastUsedAddressIndex = account.GetLastUsedAddress(a.IsChange).Index;
int addressesCount = a.IsChange ? account.InternalAddresses.Count() : account.ExternalAddresses.Count();
int lastUsedAddressIndex = account.GetLastUsedAddress(isChange).Index;
int addressesCount = isChange ? account.InternalAddresses.Count() : account.ExternalAddresses.Count();
int emptyAddressesCount = addressesCount - lastUsedAddressIndex - 1;
int accountsToAdd = UnusedAddressesBuffer - emptyAddressesCount;
this.CreateAddressesInAccount(account, wallet.Network, accountsToAdd, a.IsChange);
this.CreateAddressesInAccount(account, wallet.Network, accountsToAdd, isChange);
// persists the address to the wallet file
this.SaveToFile(wallet);
}
}
// adds the address to the list of tracked addresses
this.LoadKeys(a.CoinType);
this.LoadKeysLookup();
}
/// <inheritdoc />
......@@ -703,39 +696,24 @@ namespace Breeze.Wallet
}
/// <summary>
/// Loads the script pub key we're tracking for faster lookups.
/// Loads the keys and transactions we're tracking in memory for faster lookups.
/// </summary>
/// <param name="coinType">Type of the coin.</param>
/// <returns></returns>
private HashSet<Script> LoadKeys(CoinType coinType)
private void LoadKeysLookup()
{
return new HashSet<Script>(this.Wallets.
SelectMany(w => w.AccountsRoot.Where(a => a.CoinType == coinType)).
SelectMany(a => a.Accounts).
SelectMany(a => a.ExternalAddresses).
Select(s => s.ScriptPubKey));
// uncomment the following for testing on a random address
//Select(t => (new BitcoinPubKeyAddress(t.Address, Network.Main)).ScriptPubKey));
}
/// <summary>
/// Loads the transactions we're tracking in memory for faster lookups.
/// </summary>
/// <param name="coinType">Type of the coin.</param>
/// <returns></returns>
private HashSet<TransactionDetails> LoadTransactions(CoinType coinType)
this.keysLookup = new Dictionary<Script, ICollection<TransactionData>>();
foreach (var wallet in this.Wallets)
{
return new HashSet<TransactionDetails>(this.Wallets.
SelectMany(w => w.AccountsRoot.Where(a => a.CoinType == coinType)).
SelectMany(a => a.Accounts).
SelectMany(a => a.ExternalAddresses).
SelectMany(t => t.Transactions).
Select(t => new TransactionDetails
var accounts = wallet.GetAccountsByCoinType(this.coinType);
foreach (var account in accounts)
{
Hash = t.Id,
Index = t.Index,
Amount = t.Amount
}));
var addresses = account.ExternalAddresses.Concat(account.InternalAddresses);
foreach (var address in addresses)
{
this.keysLookup.Add(address.ScriptPubKey, address.Transactions);
}
}
}
}
/// <summary>
......@@ -767,19 +745,14 @@ namespace Breeze.Wallet
public class TransactionFoundEventArgs : EventArgs
{
public string WalletName { get; set; }
public string AccountName { get; set; }
public CoinType CoinType { get; set; }
public string Address { get; set; }
public bool IsChange { get; set; }
public Script Script { get; set; }
public uint256 TransactionHash { get; set; }
public TransactionFoundEventArgs(Wallet wallet, CoinType coinType, HdAccount account, HdAddress address, bool isChange)
public TransactionFoundEventArgs(Script script, uint256 transactionHash)
{
this.WalletName = wallet.Name;
this.CoinType = coinType;
this.AccountName = account.Name;
this.Address = address.Address;
this.IsChange = isChange;
this.Script = script;
this.TransactionHash = transactionHash;
}
}
}
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