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
0a7b7a87
Commit
0a7b7a87
authored
Apr 30, 2017
by
Jeremy Bokobza
Committed by
GitHub
Apr 30, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #36 from bokobza/feature/walletmanager
Added Tracker object to manage syncing of blocks
parents
36d50d41
c3c089a3
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
474 additions
and
126 deletions
+474
-126
ChainExtensions.cs
Breeze/src/Breeze.Wallet/ChainExtensions.cs
+43
-0
WalletController.cs
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
+4
-15
ITracker.cs
Breeze/src/Breeze.Wallet/ITracker.cs
+20
-0
IWalletManager.cs
Breeze/src/Breeze.Wallet/IWalletManager.cs
+18
-1
BlockObserver.cs
Breeze/src/Breeze.Wallet/Notifications/BlockObserver.cs
+15
-16
TransactionObserver.cs
...ze/src/Breeze.Wallet/Notifications/TransactionObserver.cs
+15
-13
Tracker.cs
Breeze/src/Breeze.Wallet/Tracker.cs
+93
-0
Wallet.cs
Breeze/src/Breeze.Wallet/Wallet.cs
+29
-2
WalletFeature.cs
Breeze/src/Breeze.Wallet/WalletFeature.cs
+14
-15
WalletManager.cs
Breeze/src/Breeze.Wallet/WalletManager.cs
+223
-12
ITrackerWrapper.cs
Breeze/src/Breeze.Wallet/Wrappers/ITrackerWrapper.cs
+0
-13
TrackerWrapper.cs
Breeze/src/Breeze.Wallet/Wrappers/TrackerWrapper.cs
+0
-39
No files found.
Breeze/src/Breeze.Wallet/ChainExtensions.cs
0 → 100644
View file @
0a7b7a87
using
System
;
using
System.Collections.Generic
;
using
System.Text
;
using
NBitcoin
;
namespace
Breeze.Wallet
{
public
static
class
ChainExtensions
{
/// <summary>
/// Determines whether the chain is downloaded and up to date.
/// </summary>
/// <param name="chain">The chain.</param>
public
static
bool
IsDownloaded
(
this
ConcurrentChain
chain
)
{
return
chain
.
Tip
.
Header
.
BlockTime
.
ToUnixTimeSeconds
()
>
(
DateTimeOffset
.
Now
.
ToUnixTimeSeconds
()
-
TimeSpan
.
FromHours
(
1
).
TotalSeconds
);
}
/// <summary>
/// Gets the type of the coin this chain relates to.
/// Obviously this method and how we figure out what coin we're on needs to be revisited.
/// </summary>
/// <param name="chain">The chain.</param>
/// <returns></returns>
/// <exception cref="System.Exception">No support for this coin.</exception>
public
static
CoinType
GetCoinType
(
this
ConcurrentChain
chain
)
{
uint256
genesis
=
chain
.
Genesis
.
Header
.
GetHash
();
switch
(
genesis
.
ToString
())
{
case
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
:
return
CoinType
.
Bitcoin
;
case
"b0e511e965aeb40614ca65a1b79bd6e4e7ef299fa23e575a64b079691e9d4690"
:
return
CoinType
.
Stratis
;
case
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
:
return
CoinType
.
Testnet
;
default
:
throw
new
Exception
(
"No support for this coin."
);
}
}
}
}
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
View file @
0a7b7a87
...
@@ -2,12 +2,10 @@
...
@@ -2,12 +2,10 @@
using
System.IO
;
using
System.IO
;
using
System.Linq
;
using
System.Linq
;
using
System.Net
;
using
System.Net
;
using
System.Runtime.InteropServices
;
using
System.Security
;
using
System.Security
;
using
Breeze.Wallet.Errors
;
using
Breeze.Wallet.Errors
;
using
Microsoft.AspNetCore.Mvc
;
using
Microsoft.AspNetCore.Mvc
;
using
Breeze.Wallet.Models
;
using
Breeze.Wallet.Models
;
using
Breeze.Wallet.Wrappers
;
using
NBitcoin
;
using
NBitcoin
;
namespace
Breeze.Wallet.Controllers
namespace
Breeze.Wallet.Controllers
...
@@ -122,6 +120,9 @@ namespace Breeze.Wallet.Controllers
...
@@ -122,6 +120,9 @@ namespace Breeze.Wallet.Controllers
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
Wallet
wallet
=
this
.
walletManager
.
RecoverWallet
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
,
request
.
Network
,
request
.
Mnemonic
);
Wallet
wallet
=
this
.
walletManager
.
RecoverWallet
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
,
request
.
Network
,
request
.
Mnemonic
);
// TODO give the tracker the date at which this wallet was originally created so that it can start syncing blocks for it
return
this
.
Json
(
new
WalletModel
return
this
.
Json
(
new
WalletModel
{
{
Network
=
wallet
.
Network
.
Name
,
Network
=
wallet
.
Network
.
Name
,
...
@@ -372,23 +373,11 @@ namespace Breeze.Wallet.Controllers
...
@@ -372,23 +373,11 @@ namespace Breeze.Wallet.Controllers
{
{
if
(
string
.
IsNullOrEmpty
(
folderPath
))
if
(
string
.
IsNullOrEmpty
(
folderPath
))
{
{
folderPath
=
GetDefaultWalletFolderPath
();
folderPath
=
WalletManager
.
GetDefaultWalletFolderPath
();
}
}
return
Directory
.
CreateDirectory
(
folderPath
);
return
Directory
.
CreateDirectory
(
folderPath
);
}
}
/// <summary>
/// Gets the path of the default folder in which the wallets will be stored.
/// </summary>
/// <returns>The folder path for Windows, Linux or OSX systems.</returns>
private
static
string
GetDefaultWalletFolderPath
()
{
if
(
RuntimeInformation
.
IsOSPlatform
(
OSPlatform
.
Windows
))
{
return
$@"
{
Environment
.
GetEnvironmentVariable
(
"AppData"
)}
\Breeze"
;
}
return
$"
{
Environment
.
GetEnvironmentVariable
(
"HOME"
)}
/.breeze"
;
}
}
}
}
}
Breeze/src/Breeze.Wallet/ITracker.cs
0 → 100644
View file @
0a7b7a87
using
System.Threading.Tasks
;
using
NBitcoin
;
namespace
Breeze.Wallet
{
public
interface
ITracker
{
/// <summary>
/// Initializes the tracker.
/// </summary>
/// <returns></returns>
Task
Initialize
();
/// <summary>
/// Waits for the chain to download.
/// </summary>
/// <returns></returns>
Task
WaitForChainDownloadAsync
();
}
}
Breeze/src/Breeze.Wallet/IWalletManager.cs
View file @
0a7b7a87
...
@@ -66,7 +66,7 @@ namespace Breeze.Wallet
...
@@ -66,7 +66,7 @@ namespace Breeze.Wallet
/// Creates the new address.
/// Creates the new address.
/// </summary>
/// </summary>
/// <param name="walletName">The name of the wallet in which this address will be created.</param>
/// <param name="walletName">The name of the wallet in which this address will be created.</param>
/// <param name="coinType">
t
he type of coin for which to create an account.</param>
/// <param name="coinType">
T
he type of coin for which to create an account.</param>
/// <param name="accountName">The name of the account in which this address will be created.</param>
/// <param name="accountName">The name of the account in which this address will be created.</param>
/// <returns>The new address, in Base58 format.</returns>
/// <returns>The new address, in Base58 format.</returns>
string
CreateNewAddress
(
string
walletName
,
CoinType
coinType
,
string
accountName
);
string
CreateNewAddress
(
string
walletName
,
CoinType
coinType
,
string
accountName
);
...
@@ -80,5 +80,22 @@ namespace Breeze.Wallet
...
@@ -80,5 +80,22 @@ namespace Breeze.Wallet
WalletBuildTransactionModel
BuildTransaction
(
string
password
,
string
address
,
Money
amount
,
string
feeType
,
bool
allowUnconfirmed
);
WalletBuildTransactionModel
BuildTransaction
(
string
password
,
string
address
,
Money
amount
,
string
feeType
,
bool
allowUnconfirmed
);
bool
SendTransaction
(
string
transactionHex
);
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
);
/// <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
);
}
}
}
}
Breeze/src/Breeze.Wallet/Notifications/BlockObserver.cs
View file @
0a7b7a87
using
NBitcoin
;
using
NBitcoin
;
using
Stratis.Bitcoin
;
using
Stratis.Bitcoin
;
using
Breeze.Wallet.Wrappers
;
using
Stratis.Bitcoin.Builder
;
namespace
Breeze.Wallet.Notifications
namespace
Breeze.Wallet.Notifications
{
{
...
@@ -9,27 +7,28 @@ namespace Breeze.Wallet.Notifications
...
@@ -9,27 +7,28 @@ namespace Breeze.Wallet.Notifications
/// Observer that receives notifications about the arrival of new <see cref="Block"/>s.
/// Observer that receives notifications about the arrival of new <see cref="Block"/>s.
/// </summary>
/// </summary>
public
class
BlockObserver
:
SignalObserver
<
Block
>
public
class
BlockObserver
:
SignalObserver
<
Block
>
{
{
private
readonly
ConcurrentChain
chain
;
private
readonly
ConcurrentChain
chain
;
private
readonly
CoinType
coinType
;
private
readonly
IWalletManager
walletManager
;
private
readonly
ITrackerWrapper
trackerWrapper
;
public
BlockObserver
(
ConcurrentChain
chain
,
CoinType
coinType
,
IWalletManager
walletManager
)
{
this
.
chain
=
chain
;
this
.
coinType
=
coinType
;
this
.
walletManager
=
walletManager
;
}
public
BlockObserver
(
ConcurrentChain
chain
,
ITrackerWrapper
trackerWrapper
)
{
this
.
chain
=
chain
;
this
.
trackerWrapper
=
trackerWrapper
;
}
/// <summary>
/// <summary>
/// Manages what happens when a new block is received.
/// Manages what happens when a new block is received.
/// </summary>
/// </summary>
/// <param name="block">The new block</param>
/// <param name="block">The new block</param>
protected
override
void
OnNextCore
(
Block
block
)
protected
override
void
OnNextCore
(
Block
block
)
{
{
var
hash
=
block
.
Header
.
GetHash
();
var
hash
=
block
.
Header
.
GetHash
();
var
height
=
this
.
chain
.
GetBlock
(
hash
).
Height
;
var
height
=
this
.
chain
.
GetBlock
(
hash
).
Height
;
this
.
trackerWrapper
.
NotifyAboutBlock
(
height
,
block
);
this
.
walletManager
.
ProcessBlock
(
this
.
coinType
,
height
,
block
);
}
}
}
}
}
}
Breeze/src/Breeze.Wallet/Notifications/TransactionObserver.cs
View file @
0a7b7a87
using
NBitcoin
;
using
NBitcoin
;
using
Stratis.Bitcoin
;
using
Stratis.Bitcoin
;
using
Breeze.Wallet.Wrappers
;
namespace
Breeze.Wallet.Notifications
namespace
Breeze.Wallet.Notifications
{
{
...
@@ -8,21 +7,24 @@ namespace Breeze.Wallet.Notifications
...
@@ -8,21 +7,24 @@ namespace Breeze.Wallet.Notifications
/// Observer that receives notifications about the arrival of new <see cref="Transaction"/>s.
/// Observer that receives notifications about the arrival of new <see cref="Transaction"/>s.
/// </summary>
/// </summary>
public
class
TransactionObserver
:
SignalObserver
<
Transaction
>
public
class
TransactionObserver
:
SignalObserver
<
Transaction
>
{
{
private
readonly
ITrackerWrapper
trackerWrapper
;
private
readonly
CoinType
coinType
;
private
readonly
IWalletManager
walletManager
;
public
TransactionObserver
(
CoinType
coinType
,
IWalletManager
walletManager
)
{
this
.
coinType
=
coinType
;
this
.
walletManager
=
walletManager
;
}
public
TransactionObserver
(
ITrackerWrapper
trackerWrapper
)
{
this
.
trackerWrapper
=
trackerWrapper
;
}
/// <summary>
/// <summary>
/// Manages what happens when a new transaction is received.
/// Manages what happens when a new transaction is received.
/// </summary>
/// </summary>
/// <param name="transaction">The new transaction</param>
/// <param name="transaction">The new transaction</param>
protected
override
void
OnNextCore
(
Transaction
transaction
)
protected
override
void
OnNextCore
(
Transaction
transaction
)
{
{
this
.
trackerWrapper
.
NotifyAboutTransaction
(
transaction
);
this
.
walletManager
.
ProcessTransaction
(
this
.
coinType
,
transaction
);
}
}
}
}
}
}
Breeze/src/Breeze.Wallet/Tracker.cs
0 → 100644
View file @
0a7b7a87
using
System
;
using
System.Collections.Generic
;
using
System.Collections.ObjectModel
;
using
System.Linq
;
using
System.Reactive.Linq
;
using
System.Text
;
using
System.Threading
;
using
System.Threading.Tasks
;
using
Breeze.Wallet.Notifications
;
using
NBitcoin
;
using
Stratis.Bitcoin
;
using
Stratis.Bitcoin.Notifications
;
using
Stratis.Bitcoin.Utilities
;
namespace
Breeze.Wallet
{
public
class
Tracker
:
ITracker
{
private
readonly
WalletManager
walletManager
;
private
readonly
ConcurrentChain
chain
;
private
readonly
Signals
signals
;
private
readonly
BlockNotification
blockNotification
;
private
readonly
CoinType
coinType
;
public
Tracker
(
IWalletManager
walletManager
,
ConcurrentChain
chain
,
Signals
signals
,
BlockNotification
blockNotification
)
{
this
.
walletManager
=
walletManager
as
WalletManager
;
this
.
chain
=
chain
;
this
.
signals
=
signals
;
this
.
blockNotification
=
blockNotification
;
this
.
coinType
=
chain
.
GetCoinType
();
}
/// <inheritdoc />
public
async
Task
Initialize
()
{
// get the chain headers. This needs to be up-to-date before we really do anything
await
this
.
WaitForChainDownloadAsync
();
// subscribe to receiving blocks and transactions
BlockSubscriber
sub
=
new
BlockSubscriber
(
this
.
signals
.
Blocks
,
new
BlockObserver
(
this
.
chain
,
this
.
coinType
,
this
.
walletManager
));
sub
.
Subscribe
();
TransactionSubscriber
txSub
=
new
TransactionSubscriber
(
this
.
signals
.
Transactions
,
new
TransactionObserver
(
this
.
coinType
,
this
.
walletManager
));
txSub
.
Subscribe
();
// start syncing blocks
this
.
blockNotification
.
SyncFrom
(
this
.
chain
.
GetBlock
(
this
.
FindBestHeightForSyncing
()).
HashBlock
);
}
private
int
FindBestHeightForSyncing
()
{
// if there are no wallets, get blocks from now
if
(!
this
.
walletManager
.
Wallets
.
Any
())
{
return
this
.
chain
.
Tip
.
Height
;
}
// sync the accounts with new blocks, starting from the most out of date
int
?
syncFromHeight
=
this
.
walletManager
.
Wallets
.
Min
(
w
=>
w
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
this
.
coinType
).
LastBlockSyncedHeight
);
if
(
syncFromHeight
==
null
)
{
return
this
.
chain
.
Tip
.
Height
;
}
return
Math
.
Min
(
syncFromHeight
.
Value
,
this
.
chain
.
Tip
.
Height
);
}
/// <inheritdoc />
public
Task
WaitForChainDownloadAsync
()
{
// make sure the chain is downloaded
CancellationTokenSource
cancellationTokenSource
=
new
CancellationTokenSource
();
return
AsyncLoop
.
Run
(
"WalletFeature.DownloadChain"
,
token
=>
{
// wait until the chain is downloaded. We wait until a block is from an hour ago.
if
(
this
.
chain
.
IsDownloaded
())
{
cancellationTokenSource
.
Cancel
();
}
return
Task
.
CompletedTask
;
},
cancellationTokenSource
.
Token
,
repeatEvery
:
TimeSpans
.
FiveSeconds
);
}
private
bool
BlocksSynced
()
{
return
this
.
walletManager
.
Wallets
.
All
(
w
=>
w
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
this
.
coinType
).
LastBlockSyncedHeight
==
this
.
chain
.
Tip
.
Height
);
}
}
}
Breeze/src/Breeze.Wallet/Wallet.cs
View file @
0a7b7a87
...
@@ -69,6 +69,12 @@ namespace Breeze.Wallet
...
@@ -69,6 +69,12 @@ namespace Breeze.Wallet
[
JsonProperty
(
PropertyName
=
"coinType"
)]
[
JsonProperty
(
PropertyName
=
"coinType"
)]
public
CoinType
CoinType
{
get
;
set
;
}
public
CoinType
CoinType
{
get
;
set
;
}
/// <summary>
/// The height of the last block that was synced.
/// </summary>
[
JsonProperty
(
PropertyName
=
"lastBlockSyncedHeight"
,
NullValueHandling
=
NullValueHandling
.
Ignore
)]
public
int
?
LastBlockSyncedHeight
{
get
;
set
;
}
/// <summary>
/// <summary>
/// The accounts used in the wallet.
/// The accounts used in the wallet.
/// </summary>
/// </summary>
...
@@ -79,9 +85,22 @@ namespace Breeze.Wallet
...
@@ -79,9 +85,22 @@ namespace Breeze.Wallet
/// <summary>
/// <summary>
/// The type of coin, as specified in BIP44.
/// The type of coin, as specified in BIP44.
/// </summary>
/// </summary>
/// <remarks>For more, see https://github.com/satoshilabs/slips/blob/master/slip-0044.md</remarks>
public
enum
CoinType
public
enum
CoinType
{
{
/// <summary>
/// Bitcoin
/// </summary>
Bitcoin
=
0
,
Bitcoin
=
0
,
/// <summary>
/// Testnet (all coins)
/// </summary>
Testnet
=
1
,
/// <summary>
/// Stratis
/// </summary>
Stratis
=
105
Stratis
=
105
}
}
...
@@ -191,19 +210,27 @@ namespace Breeze.Wallet
...
@@ -191,19 +210,27 @@ namespace Breeze.Wallet
/// Transaction id.
/// Transaction id.
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"id"
)]
[
JsonProperty
(
PropertyName
=
"id"
)]
public
string
Id
{
get
;
set
;
}
[
JsonConverter
(
typeof
(
UInt256JsonConverter
))]
public
uint256
Id
{
get
;
set
;
}
/// <summary>
/// <summary>
/// The transaction amount.
/// The transaction amount.
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"amount"
)]
[
JsonProperty
(
PropertyName
=
"amount"
)]
[
JsonConverter
(
typeof
(
MoneyJsonConverter
))]
public
Money
Amount
{
get
;
set
;
}
public
Money
Amount
{
get
;
set
;
}
/// <summary>
/// The index of this scriptPubKey in the transaction it is contained.
/// </summary>
[
JsonProperty
(
PropertyName
=
"index"
)]
public
int
?
Index
{
get
;
set
;
}
/// <summary>
/// <summary>
/// The height of the block including this transaction.
/// The height of the block including this transaction.
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"blockHeight"
)]
[
JsonProperty
(
PropertyName
=
"blockHeight"
)]
public
int
BlockHeight
{
get
;
set
;
}
public
int
?
BlockHeight
{
get
;
set
;
}
/// <summary>
/// <summary>
/// Whether this transaction has been confirmed or not.
/// Whether this transaction has been confirmed or not.
...
...
Breeze/src/Breeze.Wallet/WalletFeature.cs
View file @
0a7b7a87
using
Stratis.Bitcoin.Builder.Feature
;
using
Stratis.Bitcoin.Builder.Feature
;
using
Breeze.Wallet.Controllers
;
using
Breeze.Wallet.Controllers
;
using
Breeze.Wallet.Notifications
;
using
Breeze.Wallet.Wrappers
;
using
Microsoft.Extensions.DependencyInjection
;
using
Microsoft.Extensions.DependencyInjection
;
using
NBitcoin
;
using
NBitcoin
;
using
Stratis.Bitcoin
;
using
Stratis.Bitcoin
;
...
@@ -11,23 +9,24 @@ namespace Breeze.Wallet
...
@@ -11,23 +9,24 @@ namespace Breeze.Wallet
{
{
public
class
WalletFeature
:
FullNodeFeature
public
class
WalletFeature
:
FullNodeFeature
{
{
private
readonly
ITrackerWrapper
trackerWrapper
;
private
readonly
ITracker
tracker
;
private
readonly
Signals
signals
;
private
readonly
IWalletManager
walletManager
;
private
readonly
ConcurrentChain
chain
;
public
WalletFeature
(
ITracker
tracker
,
IWalletManager
walletManager
)
public
WalletFeature
(
ITrackerWrapper
trackerWrapper
,
Signals
signals
,
ConcurrentChain
chain
)
{
{
this
.
trackerWrapper
=
trackerWrapper
;
this
.
tracker
=
tracker
;
this
.
signals
=
signals
;
this
.
walletManager
=
walletManager
;
this
.
chain
=
chain
;
}
}
public
override
void
Start
()
public
override
void
Start
()
{
this
.
tracker
.
Initialize
();
}
public
override
void
Stop
()
{
{
BlockSubscriber
sub
=
new
BlockSubscriber
(
signals
.
Blocks
,
new
BlockObserver
(
chain
,
trackerWrapper
));
this
.
walletManager
.
Dispose
();
sub
.
Subscribe
();
base
.
Stop
();
TransactionSubscriber
txSub
=
new
TransactionSubscriber
(
signals
.
Transactions
,
new
TransactionObserver
(
trackerWrapper
));
txSub
.
Subscribe
();
}
}
}
}
...
@@ -41,7 +40,7 @@ namespace Breeze.Wallet
...
@@ -41,7 +40,7 @@ namespace Breeze.Wallet
.
AddFeature
<
WalletFeature
>()
.
AddFeature
<
WalletFeature
>()
.
FeatureServices
(
services
=>
.
FeatureServices
(
services
=>
{
{
services
.
AddSingleton
<
ITracker
Wrapper
,
TrackerWrapp
er
>();
services
.
AddSingleton
<
ITracker
,
Track
er
>();
services
.
AddSingleton
<
IWalletManager
,
WalletManager
>();
services
.
AddSingleton
<
IWalletManager
,
WalletManager
>();
services
.
AddSingleton
<
WalletController
>();
services
.
AddSingleton
<
WalletController
>();
});
});
...
...
Breeze/src/Breeze.Wallet/WalletManager.cs
View file @
0a7b7a87
...
@@ -2,11 +2,12 @@
...
@@ -2,11 +2,12 @@
using
System.Collections.Generic
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.IO
;
using
System.Linq
;
using
System.Linq
;
using
System.Runtime.InteropServices
;
using
Breeze.Wallet.Helpers
;
using
Breeze.Wallet.Helpers
;
using
Breeze.Wallet.Models
;
using
Breeze.Wallet.Models
;
using
NBitcoin
;
using
NBitcoin
;
using
Newtonsoft.Json
;
using
Newtonsoft.Json
;
using
Stratis.Bitcoin.Utilities
;
using
Transaction
=
NBitcoin
.
Transaction
;
namespace
Breeze.Wallet
namespace
Breeze.Wallet
{
{
...
@@ -17,9 +18,24 @@ namespace Breeze.Wallet
...
@@ -17,9 +18,24 @@ namespace Breeze.Wallet
{
{
public
List
<
Wallet
>
Wallets
{
get
;
}
public
List
<
Wallet
>
Wallets
{
get
;
}
public
HashSet
<
Script
>
PubKeys
{
get
;
}
public
HashSet
<
TransactionDetails
>
TrackedTransactions
{
get
;
}
public
WalletManager
()
public
WalletManager
()
{
{
this
.
Wallets
=
new
List
<
Wallet
>();
this
.
Wallets
=
new
List
<
Wallet
>();
// find wallets and load them in memory
foreach
(
var
path
in
this
.
GetWalletFilesPaths
())
{
this
.
Load
(
this
.
GetWallet
(
path
));
}
// load data in memory for faster lookups
// TODO get the coin type from somewhere else
this
.
PubKeys
=
this
.
LoadKeys
(
CoinType
.
Bitcoin
);
this
.
TrackedTransactions
=
this
.
LoadTransactions
(
CoinType
.
Bitcoin
);
}
}
/// <inheritdoc />
/// <inheritdoc />
...
@@ -37,6 +53,7 @@ namespace Breeze.Wallet
...
@@ -37,6 +53,7 @@ namespace Breeze.Wallet
ExtKey
extendedKey
=
mnemonic
.
DeriveExtKey
(
passphrase
);
ExtKey
extendedKey
=
mnemonic
.
DeriveExtKey
(
passphrase
);
// create a wallet file
// create a wallet file
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
WalletHelpers
.
GetNetwork
(
network
),
extendedKey
);
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
WalletHelpers
.
GetNetwork
(
network
),
extendedKey
);
this
.
Load
(
wallet
);
this
.
Load
(
wallet
);
...
@@ -48,11 +65,8 @@ namespace Breeze.Wallet
...
@@ -48,11 +65,8 @@ namespace Breeze.Wallet
{
{
string
walletFilePath
=
Path
.
Combine
(
folderPath
,
$"
{
name
}
.json"
);
string
walletFilePath
=
Path
.
Combine
(
folderPath
,
$"
{
name
}
.json"
);
if
(!
File
.
Exists
(
walletFilePath
))
throw
new
FileNotFoundException
(
$"No wallet file found at
{
walletFilePath
}
"
);
// load the file from the local system
// load the file from the local system
Wallet
wallet
=
JsonConvert
.
DeserializeObject
<
Wallet
>(
File
.
ReadAllText
(
walletFilePath
)
);
Wallet
wallet
=
this
.
GetWallet
(
walletFilePath
);
this
.
Load
(
wallet
);
this
.
Load
(
wallet
);
return
wallet
;
return
wallet
;
...
@@ -179,8 +193,11 @@ namespace Breeze.Wallet
...
@@ -179,8 +193,11 @@ namespace Breeze.Wallet
CreationTime
=
DateTimeOffset
.
Now
CreationTime
=
DateTimeOffset
.
Now
}});
}});
// persists the address to the wallet file
this
.
SaveToFile
(
wallet
);
this
.
SaveToFile
(
wallet
);
// adds the address to the list of tracked addresses
this
.
PubKeys
.
Add
(
address
.
ScriptPubKey
);
return
address
.
ToString
();
return
address
.
ToString
();
}
}
...
@@ -199,8 +216,7 @@ namespace Breeze.Wallet
...
@@ -199,8 +216,7 @@ namespace Breeze.Wallet
throw
new
System
.
NotImplementedException
();
throw
new
System
.
NotImplementedException
();
}
}
public
WalletBuildTransactionModel
BuildTransaction
(
string
password
,
string
address
,
Money
amount
,
string
feeType
,
public
WalletBuildTransactionModel
BuildTransaction
(
string
password
,
string
address
,
Money
amount
,
string
feeType
,
bool
allowUnconfirmed
)
bool
allowUnconfirmed
)
{
{
throw
new
System
.
NotImplementedException
();
throw
new
System
.
NotImplementedException
();
}
}
...
@@ -210,6 +226,111 @@ namespace Breeze.Wallet
...
@@ -210,6 +226,111 @@ namespace Breeze.Wallet
throw
new
System
.
NotImplementedException
();
throw
new
System
.
NotImplementedException
();
}
}
/// <inheritdoc />
public
void
ProcessBlock
(
CoinType
coinType
,
int
height
,
Block
block
)
{
Console
.
WriteLine
(
$"block notification: height:
{
height
}
, block hash:
{
block
.
Header
.
GetHash
()}
, coin type:
{
coinType
}
"
);
foreach
(
Transaction
transaction
in
block
.
Transactions
)
{
this
.
ProcessTransaction
(
coinType
,
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
))
{
accountRoot
.
LastBlockSyncedHeight
=
height
;
}
}
}
/// <inheritdoc />
public
void
ProcessTransaction
(
CoinType
coinType
,
Transaction
transaction
,
int
?
blockHeight
=
null
,
uint
?
blockTime
=
null
)
{
Console
.
WriteLine
(
$"transaction notification: tx hash
{
transaction
.
GetHash
()}
, coin type:
{
coinType
}
"
);
foreach
(
var
k
in
this
.
PubKeys
)
{
// check if the outputs contain one of our addresses
var
utxo
=
transaction
.
Outputs
.
SingleOrDefault
(
o
=>
k
==
o
.
ScriptPubKey
);
if
(
utxo
!=
null
)
{
AddTransactionToWallet
(
coinType
,
transaction
.
GetHash
(),
transaction
.
Time
,
transaction
.
Outputs
.
IndexOf
(
utxo
),
utxo
.
Value
,
k
,
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
);
// 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
,
k
,
blockHeight
,
blockTime
);
}
}
}
}
/// <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>
/// <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>
private
void
AddTransactionToWallet
(
CoinType
coinType
,
uint256
transactionHash
,
uint
time
,
int
?
index
,
Money
amount
,
Script
script
,
int
?
blockHeight
=
null
,
uint
?
blockTime
=
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
)
{
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
}
});
}
}
}
}
this
.
TrackedTransactions
.
Add
(
new
TransactionDetails
{
Hash
=
transactionHash
,
Index
=
index
,
Amount
=
amount
});
}
}
/// <inheritdoc />
/// <inheritdoc />
public
void
DeleteWallet
(
string
walletFilePath
)
public
void
DeleteWallet
(
string
walletFilePath
)
{
{
...
@@ -219,7 +340,11 @@ namespace Breeze.Wallet
...
@@ -219,7 +340,11 @@ namespace Breeze.Wallet
/// <inheritdoc />
/// <inheritdoc />
public
void
Dispose
()
public
void
Dispose
()
{
{
// TODO Safely persist the wallet before disposing
// safely persist the wallets to the file system before disposing
foreach
(
var
wallet
in
this
.
Wallets
)
{
this
.
SaveToFile
(
wallet
);
}
}
}
/// <summary>
/// <summary>
...
@@ -230,7 +355,6 @@ namespace Breeze.Wallet
...
@@ -230,7 +355,6 @@ namespace Breeze.Wallet
/// <param name="name">The name of the wallet.</param>
/// <param name="name">The name of the wallet.</param>
/// <param name="network">The network this wallet is for.</param>
/// <param name="network">The network this wallet is for.</param>
/// <param name="extendedKey">The root key used to generate keys.</param>
/// <param name="extendedKey">The root key used to generate keys.</param>
/// <param name="coinType">The type of coin for which this wallet is created.</param>
/// <param name="creationTime">The time this wallet was created.</param>
/// <param name="creationTime">The time this wallet was created.</param>
/// <returns></returns>
/// <returns></returns>
/// <exception cref="System.NotSupportedException"></exception>
/// <exception cref="System.NotSupportedException"></exception>
...
@@ -250,8 +374,10 @@ namespace Breeze.Wallet
...
@@ -250,8 +374,10 @@ namespace Breeze.Wallet
Network
=
network
,
Network
=
network
,
AccountsRoot
=
new
List
<
AccountRoot
>
{
AccountsRoot
=
new
List
<
AccountRoot
>
{
new
AccountRoot
{
Accounts
=
new
List
<
HdAccount
>(),
CoinType
=
CoinType
.
Bitcoin
},
new
AccountRoot
{
Accounts
=
new
List
<
HdAccount
>(),
CoinType
=
CoinType
.
Bitcoin
},
new
AccountRoot
{
Accounts
=
new
List
<
HdAccount
>(),
CoinType
=
CoinType
.
Testnet
},
new
AccountRoot
{
Accounts
=
new
List
<
HdAccount
>(),
CoinType
=
CoinType
.
Stratis
}
},
new
AccountRoot
{
Accounts
=
new
List
<
HdAccount
>(),
CoinType
=
CoinType
.
Stratis
}
},
WalletFilePath
=
walletFilePath
WalletFilePath
=
walletFilePath
,
};
};
// create a folder if none exists and persist the file
// create a folder if none exists and persist the file
...
@@ -270,16 +396,33 @@ namespace Breeze.Wallet
...
@@ -270,16 +396,33 @@ namespace Breeze.Wallet
File
.
WriteAllText
(
wallet
.
WalletFilePath
,
JsonConvert
.
SerializeObject
(
wallet
,
Formatting
.
Indented
));
File
.
WriteAllText
(
wallet
.
WalletFilePath
,
JsonConvert
.
SerializeObject
(
wallet
,
Formatting
.
Indented
));
}
}
/// <summary>
/// Gets the wallet located at the specified path.
/// </summary>
/// <param name="walletFilePath">The wallet file path.</param>
/// <returns></returns>
/// <exception cref="System.IO.FileNotFoundException"></exception>
private
Wallet
GetWallet
(
string
walletFilePath
)
{
if
(!
File
.
Exists
(
walletFilePath
))
throw
new
FileNotFoundException
(
$"No wallet file found at
{
walletFilePath
}
"
);
// load the file from the local system
return
JsonConvert
.
DeserializeObject
<
Wallet
>(
File
.
ReadAllText
(
walletFilePath
));
}
/// <summary>
/// <summary>
/// Loads the wallet to be used by the manager.
/// Loads the wallet to be used by the manager.
/// </summary>
/// </summary>
/// <param name="wallet">The wallet to load.</param>
/// <param name="wallet">The wallet to load.</param>
private
void
Load
(
Wallet
wallet
)
private
void
Load
(
Wallet
wallet
)
{
{
if
(
this
.
Wallets
.
A
ll
(
w
=>
w
.
Name
!
=
wallet
.
Name
))
if
(
this
.
Wallets
.
A
ny
(
w
=>
w
.
Name
=
=
wallet
.
Name
))
{
{
this
.
Wallets
.
Add
(
wallet
)
;
return
;
}
}
this
.
Wallets
.
Add
(
wallet
);
}
}
private
BitcoinPubKeyAddress
GenerateAddress
(
string
accountExtPubKey
,
int
index
,
bool
isChange
,
Network
network
)
private
BitcoinPubKeyAddress
GenerateAddress
(
string
accountExtPubKey
,
int
index
,
bool
isChange
,
Network
network
)
...
@@ -290,6 +433,14 @@ namespace Breeze.Wallet
...
@@ -290,6 +433,14 @@ namespace Breeze.Wallet
return
extPubKey
.
PubKey
.
GetAddress
(
network
);
return
extPubKey
.
PubKey
.
GetAddress
(
network
);
}
}
private
IEnumerable
<
string
>
GetWalletFilesPaths
()
{
// TODO look in user-chosen folder as well.
// maybe the api can maintain a list of wallet paths it knows about
var
defaultFolderPath
=
GetDefaultWalletFolderPath
();
return
Directory
.
EnumerateFiles
(
defaultFolderPath
,
"*.json"
,
SearchOption
.
TopDirectoryOnly
);
}
/// <summary>
/// <summary>
/// Creates the bip44 path.
/// Creates the bip44 path.
/// </summary>
/// </summary>
...
@@ -306,5 +457,65 @@ namespace Breeze.Wallet
...
@@ -306,5 +457,65 @@ namespace Breeze.Wallet
int
change
=
isChange
?
1
:
0
;
int
change
=
isChange
?
1
:
0
;
return
$"m/44'/
{(
int
)
coinType
}
'/
{
accountIndex
}
'/
{
change
}
/
{
addressIndex
}
"
;
return
$"m/44'/
{(
int
)
coinType
}
'/
{
accountIndex
}
'/
{
change
}
/
{
addressIndex
}
"
;
}
}
/// <summary>
/// Gets the path of the default folder in which the wallets will be stored.
/// </summary>
/// <returns>The folder path for Windows, Linux or OSX systems.</returns>
public
static
string
GetDefaultWalletFolderPath
()
{
if
(
RuntimeInformation
.
IsOSPlatform
(
OSPlatform
.
Windows
))
{
return
$@"
{
Environment
.
GetEnvironmentVariable
(
"AppData"
)}
\Breeze"
;
}
return
$"
{
Environment
.
GetEnvironmentVariable
(
"HOME"
)}
/.breeze"
;
}
/// <summary>
/// Loads the script pub key we're tracking for faster lookups.
/// </summary>
/// <param name="coinType">Type of the coin.</param>
/// <returns></returns>
private
HashSet
<
Script
>
LoadKeys
(
CoinType
coinType
)
{
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
{
Hash
=
t
.
Id
,
Index
=
t
.
Index
,
Amount
=
t
.
Amount
}));
}
}
public
class
TransactionDetails
{
public
uint256
Hash
{
get
;
set
;
}
public
int
?
Index
{
get
;
set
;
}
public
Money
Amount
{
get
;
internal
set
;
}
}
}
}
}
Breeze/src/Breeze.Wallet/Wrappers/ITrackerWrapper.cs
deleted
100644 → 0
View file @
36d50d41
using
NBitcoin
;
namespace
Breeze.Wallet.Wrappers
{
public
interface
ITrackerWrapper
{
void
NotifyAboutBlock
(
int
height
,
Block
block
);
void
NotifyAboutTransaction
(
Transaction
transaction
);
uint256
GetLastProcessedBlock
();
}
}
Breeze/src/Breeze.Wallet/Wrappers/TrackerWrapper.cs
deleted
100644 → 0
View file @
36d50d41
using
NBitcoin
;
using
System
;
namespace
Breeze.Wallet.Wrappers
{
public
class
TrackerWrapper
:
ITrackerWrapper
{
// private readonly Tracker tracker;
public
TrackerWrapper
(
Network
network
)
{
//this.tracker = new Tracker(network);
}
/// <summary>
/// Get the hash of the last block that has been succesfully processed.
/// </summary>
/// <returns>The hash of the block</returns>
public
uint256
GetLastProcessedBlock
()
{
// TODO use Tracker.BestHeight. Genesis hash for now.
return
uint256
.
Parse
(
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
);
}
public
void
NotifyAboutBlock
(
int
height
,
Block
block
)
{
// this.tracker.AddOrReplaceBlock(new Height(height), block);
Console
.
WriteLine
(
$"block notification: height:
{
height
}
, block hash:
{
block
.
Header
.
GetHash
()}
"
);
}
public
void
NotifyAboutTransaction
(
Transaction
transaction
)
{
// TODO what should the height be? is it necessary?
// this.tracker.ProcessTransaction(new SmartTransaction(transaction, new Height(0)));
Console
.
WriteLine
(
$"transaction notification: tx hash
{
transaction
.
GetHash
()}
"
);
}
}
}
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