Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
Breeze
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
3
Issues
3
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DeStream-public
Breeze
Commits
36cf64fb
Commit
36cf64fb
authored
May 15, 2017
by
Dan Gershony
Committed by
GitHub
May 15, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #52 from bokobza/feature/build-transaction
Sending funds
parents
86d060a2
3c3778b8
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
196 additions
and
215 deletions
+196
-215
ApiSpecification.md
Breeze.Documentation/ApiSpecification.md
+16
-3
Breeze.Api.Tests.csproj
Breeze/src/Breeze.Api.Tests/Breeze.Api.Tests.csproj
+2
-2
ControllersTests.cs
Breeze/src/Breeze.Api.Tests/ControllersTests.cs
+4
-12
Wallet.postman_collection.json
...Api.Tests/Postman requests/Wallet.postman_collection.json
+4
-4
WalletController.cs
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
+6
-18
IWalletManager.cs
Breeze/src/Breeze.Wallet/IWalletManager.cs
+8
-5
BlockObserver.cs
Breeze/src/Breeze.Wallet/Notifications/BlockObserver.cs
+2
-4
TransactionObserver.cs
...ze/src/Breeze.Wallet/Notifications/TransactionObserver.cs
+3
-6
Tracker.cs
Breeze/src/Breeze.Wallet/Tracker.cs
+2
-2
Wallet.cs
Breeze/src/Breeze.Wallet/Wallet.cs
+21
-4
WalletManager.cs
Breeze/src/Breeze.Wallet/WalletManager.cs
+128
-155
No files found.
Breeze.Documentation/ApiSpecification.md
View file @
36cf64fb
...
...
@@ -147,10 +147,18 @@ POST /wallet/send-transaction - Attempts to send a transaction
## POST /wallet/load - Loads the wallet and starts syncing
### 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.
...
...
Breeze/src/Breeze.Api.Tests/Breeze.Api.Tests.csproj
View file @
36cf64fb
...
...
@@ -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>
Breeze/src/Breeze.Api.Tests/ControllersTests.cs
View file @
36cf64fb
...
...
@@ -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
]
...
...
Breeze/src/Breeze.Api.Tests/Postman requests/Wallet.postman_collection.json
View file @
36cf64fb
...
...
@@ -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
\"
:
\"
myFirstW
allet
\"\n
}"
"raw"
:
"{
\n\t\"
password
\"
:
\"
123456
\"
,
\n\t\"
network
\"
:
\"
testnet
\"
,
\n\t\"
name
\"
:
\"
testw
allet
\"\n
}"
},
"description"
:
""
},
...
...
@@ -161,7 +161,7 @@
],
"body"
:
{
"mode"
:
"raw"
,
"raw"
:
"{
\r\n
\"
password
\"
:
\"
password
\"
,
\r\n
\"
a
ddress
\"
:
\"
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
\"
destinationA
ddress
\"
:
\"
1FYp9uguYCz7DgSF9jTWDeZF8kdRKQTXPg
\"
,
\r\n
\"
amount
\"
:
\"
0.12
\"
,
\r\n
\"
feeType
\"
:
\"
low
\"
,
\r\n
\"
allowUnconfirmed
\"
:
\"
true
\"\r\n
}"
},
"description"
:
""
},
...
...
@@ -206,7 +206,7 @@
"description"
:
"Gets all the wallets files stored in the default folder"
},
"response"
:
[]
},
},
{
"name"
:
"Get unused account in wallet"
,
"request"
:
{
...
...
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
View file @
36cf64fb
...
...
@@ -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
=
transaction
Result
.
hex
,
Fee
=
transactionResult
.
fee
};
return
this
.
Json
(
model
);
}
...
...
Breeze/src/Breeze.Wallet/IWalletManager.cs
View file @
36cf64fb
...
...
@@ -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
);
}
}
Breeze/src/Breeze.Wallet/Notifications/BlockObserver.cs
View file @
36cf64fb
...
...
@@ -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
);
}
}
}
Breeze/src/Breeze.Wallet/Notifications/TransactionObserver.cs
View file @
36cf64fb
...
...
@@ -7,14 +7,11 @@ namespace Breeze.Wallet.Notifications
/// Observer that receives notifications about the arrival of new <see cref="Transaction"/>s.
/// </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
(
t
his
.
coinType
,
t
ransaction
);
this
.
walletManager
.
ProcessTransaction
(
transaction
);
}
}
}
Breeze/src/Breeze.Wallet/Tracker.cs
View file @
36cf64fb
...
...
@@ -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
...
...
Breeze/src/Breeze.Wallet/Wallet.cs
View file @
36cf64fb
...
...
@@ -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
I
Enumerable
<
HdAccount
>
Accounts
{
get
;
set
;
}
public
I
Collection
<
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
I
Enumerable
<
HdAddress
>
ExternalAddresses
{
get
;
set
;
}
public
I
Collection
<
HdAddress
>
ExternalAddresses
{
get
;
set
;
}
/// <summary>
/// The list of internal addresses, typically used to receive change.
/// </summary>
[
JsonProperty
(
PropertyName
=
"internalAddresses"
)]
public
I
Enumerable
<
HdAddress
>
InternalAddresses
{
get
;
set
;
}
public
I
Collection
<
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
I
Enumerable
<
TransactionData
>
Transactions
{
get
;
set
;
}
public
I
Collection
<
TransactionData
>
Transactions
{
get
;
set
;
}
}
/// <summary>
...
...
Breeze/src/Breeze.Wallet/WalletManager.cs
View file @
36cf64fb
...
...
@@ -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
(
Con
currentChain
chain
,
Network
netwrok
)
public
WalletManager
(
Con
nectionManager
connectionManager
,
Network
netwrok
)
{
this
.
Wallets
=
new
List
<
Wallet
>();
...
...
@@ -46,12 +48,13 @@ namespace Breeze.Wallet
this
.
Load
(
this
.
DeserializeWallet
(
path
));
}
this
.
coinType
=
(
CoinType
)
netwrok
.
Consensus
.
CoinType
;
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
;
...
...
@@ -204,7 +207,7 @@ namespace Breeze.Wallet
return
newAccount
;
}
/// <inheritdoc />
public
string
GetUnusedAddress
(
string
walletName
,
CoinType
coinType
,
string
accountName
)
{
...
...
@@ -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,17 +325,17 @@ 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
)
{
throw
new
Exception
(
$"Cannot send transaction with 0
{
this
.
coinType
}
"
);
}
// get the wallet and the account
Wallet
wallet
=
this
.
GetWalletByName
(
walletName
);
Wallet
wallet
=
this
.
GetWalletByName
(
walletName
);
HdAccount
account
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
GetAccountByName
(
accountName
);
// get a list of transactions outputs that have not been spent
IEnumerable
<
TransactionData
>
spendableTransactions
=
account
.
GetSpendableTransactions
();
...
...
@@ -347,18 +350,18 @@ namespace Breeze.Wallet
// calculate which addresses needs to be used as well as the fee to be charged
var
calculationResult
=
this
.
CalculateFees
(
spendableTransactions
,
amount
);
// get extended private key
var
privateKey
=
Key
.
Parse
(
wallet
.
EncryptedSeed
,
password
,
wallet
.
Network
);
var
seedExtKey
=
new
ExtKey
(
privateKey
,
wallet
.
ChainCode
);
var
signingKeys
=
new
HashSet
<
ISecret
>();
var
coins
=
new
List
<
Coin
>();
foreach
(
var
transactionToUse
in
calculationResult
.
transactionsToUse
)
{
var
address
=
account
.
FindAddressForTransaction
(
transactionToUse
.
Id
);
ExtKey
addressExtKey
=
seedExtKey
.
Derive
(
new
KeyPath
(
address
.
HdPath
));
BitcoinExtKey
addressPrivateKey
=
addressExtKey
.
GetWif
(
wallet
.
Network
);
var
address
=
account
.
FindAddressForTransaction
(
transactionToUse
.
Id
);
ExtKey
addressExtKey
=
seedExtKey
.
Derive
(
new
KeyPath
(
address
.
HdPath
));
BitcoinExtKey
addressPrivateKey
=
addressExtKey
.
GetWif
(
wallet
.
Network
);
signingKeys
.
Add
(
addressPrivateKey
);
coins
.
Add
(
new
Coin
(
transactionToUse
.
Id
,
(
uint
)
transactionToUse
.
Index
,
transactionToUse
.
Amount
,
address
.
ScriptPubKey
));
...
...
@@ -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>
...
...
@@ -396,7 +399,7 @@ namespace Breeze.Wallet
/// <returns>The collection of transactions to be used and the fee to be charged</returns>
private
(
List
<
TransactionData
>
transactionsToUse
,
Money
fee
)
CalculateFees
(
IEnumerable
<
TransactionData
>
spendableTransactions
,
Money
amount
)
{
// TODO make this a bit smarter!
// TODO make this a bit smarter!
List
<
TransactionData
>
transactionsToUse
=
new
List
<
TransactionData
>();
foreach
(
var
transaction
in
spendableTransactions
)
{
...
...
@@ -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
)))
{
TransactionDetails
tTx
=
this
.
TrackedTransactions
.
Single
(
trackedTx
=>
trackedTx
.
Hash
==
input
.
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
)))
{
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,85 +489,66 @@ 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
)
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
)
{
// 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
))
this
.
keysLookup
.
TryGetValue
(
script
,
out
ICollection
<
TransactionData
>
trans
);
trans
.
Add
(
new
TransactionData
{
foreach
(
var
wallet
in
this
.
Wallets
)
Amount
=
amount
,
BlockHeight
=
blockHeight
,
Confirmed
=
blockHeight
.
HasValue
,
Id
=
transactionHash
,
CreationTime
=
DateTimeOffset
.
FromUnixTimeMilliseconds
(
blockTime
??
time
),
Index
=
index
});
// if this is a spending transaction, mark the spent transaction as such
if
(
spendingTransactionId
!=
null
)
{
var
transactions
=
this
.
keysLookup
.
Values
.
SelectMany
(
v
=>
v
).
Where
(
t
=>
t
.
Id
==
spendingTransactionId
);
if
(
transactions
.
Any
())
{
foreach
(
var
accountRoot
in
wallet
.
AccountsRoot
.
Where
(
a
=>
a
.
CoinType
==
coinType
))
{
foreach
(
var
account
in
accountRoot
.
Accounts
)
{
foreach
(
var
address
in
account
.
ExternalAddresses
.
Where
(
a
=>
a
.
ScriptPubKey
==
script
))
{
address
.
Transactions
=
address
.
Transactions
.
Concat
(
new
[]
{
new
TransactionData
{
Amount
=
amount
,
BlockHeight
=
blockHeight
,
Confirmed
=
blockHeight
.
HasValue
,
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
);
if
(
transactions
.
Any
())
{
transactions
.
Single
(
t
=>
t
.
Index
==
spendingTransactionIndex
).
SpentInTransaction
=
transactionHash
;
}
}
}
}
transactions
.
Single
(
t
=>
t
.
Index
==
spendingTransactionIndex
).
SpentInTransaction
=
transactionHash
;
}
this
.
TrackedTransactions
.
Add
(
new
TransactionDetails
{
Hash
=
transactionHash
,
Index
=
index
,
Amount
=
amount
});
}
// notify a transaction has been found
this
.
TransactionFound
?.
Invoke
(
this
,
new
TransactionFoundEventArgs
(
script
,
transactionHash
));
}
private
void
OnTransactionFound
(
object
sender
,
TransactionFoundEventArgs
a
)
{
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
);
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
;
}
else
{
continue
;
}
// calculate how many accounts to add to keep a buffer of 20 unused addresses
int
lastUsedAddressIndex
=
account
.
GetLastUsedAddress
(
a
.
I
sChange
).
Index
;
int
addressesCount
=
a
.
I
sChange
?
account
.
InternalAddresses
.
Count
()
:
account
.
ExternalAddresses
.
Count
();
int
emptyAddressesCount
=
addressesCount
-
lastUsedAddressIndex
-
1
;
int
accountsToAdd
=
UnusedAddressesBuffer
-
emptyAddressesCount
;
this
.
CreateAddressesInAccount
(
account
,
wallet
.
Network
,
accountsToAdd
,
a
.
I
sChange
);
// calculate how many accounts to add to keep a buffer of 20 unused addresses
int
lastUsedAddressIndex
=
account
.
GetLastUsedAddress
(
i
sChange
).
Index
;
int
addressesCount
=
i
sChange
?
account
.
InternalAddresses
.
Count
()
:
account
.
ExternalAddresses
.
Count
();
int
emptyAddressesCount
=
addressesCount
-
lastUsedAddressIndex
-
1
;
int
accountsToAdd
=
UnusedAddressesBuffer
-
emptyAddressesCount
;
this
.
CreateAddressesInAccount
(
account
,
wallet
.
Network
,
accountsToAdd
,
i
sChange
);
// persists the address to the wallet file
this
.
SaveToFile
(
wallet
);
// 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
)
{
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
this
.
keysLookup
=
new
Dictionary
<
Script
,
ICollection
<
TransactionData
>>();
foreach
(
var
wallet
in
this
.
Wallets
)
{
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
TransactionFoundEventArgs
(
Wallet
wallet
,
CoinType
coinType
,
HdAccount
account
,
HdAddress
address
,
bool
isChange
)
{
this
.
WalletName
=
wallet
.
Name
;
this
.
CoinType
=
coinType
;
this
.
AccountName
=
account
.
Name
;
this
.
Address
=
address
.
Address
;
this
.
IsChange
=
isChange
;
public
Script
Script
{
get
;
set
;
}
public
uint256
TransactionHash
{
get
;
set
;
}
public
TransactionFoundEventArgs
(
Script
script
,
uint256
transactionHash
)
{
this
.
Script
=
script
;
this
.
TransactionHash
=
transactionHash
;
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment