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
b0838544
Commit
b0838544
authored
Apr 27, 2017
by
Jeremy Bokobza
Committed by
GitHub
Apr 27, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #34 from bokobza/feature/walletmanager
Creates accounts and create addresses
parents
f24e1da0
a48af926
Show whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
2050 additions
and
439 deletions
+2050
-439
ApiSpecification.md
Breeze.Documentation/ApiSpecification.md
+31
-0
ControllersTests.cs
Breeze/src/Breeze.Api.Tests/ControllersTests.cs
+26
-26
Wallet.postman_collection.json
...Api.Tests/Postman requests/Wallet.postman_collection.json
+61
-1
Breeze.Wallet.csproj
Breeze/src/Breeze.Wallet/Breeze.Wallet.csproj
+1
-0
WalletController.cs
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
+214
-152
IWalletManager.cs
Breeze/src/Breeze.Wallet/IWalletManager.cs
+48
-11
CreateAccountModel.cs
Breeze/src/Breeze.Wallet/Models/CreateAccountModel.cs
+36
-0
CreateAddressModel.cs
Breeze/src/Breeze.Wallet/Models/CreateAddressModel.cs
+28
-0
ConcurrentObservableDictionary.cs
.../src/Breeze.Wallet/Temp/ConcurrentObservableDictionary.cs
+234
-0
ConcurrentObservableHashSet.cs
Breeze/src/Breeze.Wallet/Temp/ConcurrentObservableHashSet.cs
+75
-0
Height.cs
Breeze/src/Breeze.Wallet/Temp/Height.cs
+107
-0
SmartMerkleBlock.cs
Breeze/src/Breeze.Wallet/Temp/SmartMerkleBlock.cs
+125
-0
SmartTransaction.cs
Breeze/src/Breeze.Wallet/Temp/SmartTransaction.cs
+128
-0
Tracker.cs
Breeze/src/Breeze.Wallet/Temp/Tracker.cs
+369
-0
UnprocessedBlockBuffer.cs
Breeze/src/Breeze.Wallet/Temp/UnprocessedBlockBuffer.cs
+56
-0
Util.cs
Breeze/src/Breeze.Wallet/Temp/Util.cs
+55
-0
Wallet.cs
Breeze/src/Breeze.Wallet/Wallet.cs
+188
-3
WalletFeature.cs
Breeze/src/Breeze.Wallet/WalletFeature.cs
+36
-37
WalletFile.cs
Breeze/src/Breeze.Wallet/WalletFile.cs
+0
-40
WalletManager.cs
Breeze/src/Breeze.Wallet/WalletManager.cs
+232
-37
IWalletWrapper.cs
Breeze/src/Breeze.Wallet/Wrappers/IWalletWrapper.cs
+0
-28
WalletWrapper.cs
Breeze/src/Breeze.Wallet/Wrappers/WalletWrapper.cs
+0
-104
No files found.
Breeze.Documentation/ApiSpecification.md
View file @
b0838544
...
@@ -61,6 +61,8 @@ POST /wallet/create - Creates the wallet
...
@@ -61,6 +61,8 @@ POST /wallet/create - Creates the wallet
POST /wallet/load - Loads the wallet and starts syncing
POST /wallet/load - Loads the wallet and starts syncing
POST /wallet/recover - Recovers the wallet
POST /wallet/recover - Recovers the wallet
DELETE /wallet - Deletes the wallet
DELETE /wallet - Deletes the wallet
POST /wallet/account
POST /wallet/address
```
```
## Syncing
## Syncing
...
@@ -165,6 +167,35 @@ Cannot check if the password is good or not. If the password is wrong it'll reco
...
@@ -165,6 +167,35 @@ Cannot check if the password is good or not. If the password is wrong it'll reco
## DELETE /wallet - Deletes the wallet
## DELETE /wallet - Deletes the wallet
Works as expected.
Works as expected.
## POST /wallet/account - Adds an account to the wallet
### Parameters
```
{
"walletName": "myFirstWallet",
"accountName": "account one",
"password": "123456",
"coinType": 105
}
```
### Responses
```
"account one"
```
## POST /wallet/address - Adds an address to an account
### Parameters
```
{
"walletName": "myFirstWallet",
"accountName": "account one",
"coinType": 0
}
```
### Responses
```
"1HDypWxXWZC5KXK259EHMnrWaa2youy7Mj"
```
## GET /wallet/mempool/?allow=[true/false] - Allows or disallows mempool syncing
## GET /wallet/mempool/?allow=[true/false] - Allows or disallows mempool syncing
Works as expected.
Works as expected.
...
...
Breeze/src/Breeze.Api.Tests/ControllersTests.cs
View file @
b0838544
using
System.Collections.Generic
;
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.IO
;
using
Microsoft.AspNetCore.Mvc
;
using
Microsoft.AspNetCore.Mvc
;
using
Xunit
;
using
Xunit
;
using
Moq
;
using
Moq
;
using
Breeze.Wallet.Wrappers
;
using
Breeze.Wallet
;
using
Breeze.Wallet
;
using
Breeze.Wallet.Controllers
;
using
Breeze.Wallet.Controllers
;
using
Breeze.Wallet.Errors
;
using
Breeze.Wallet.Errors
;
using
Breeze.Wallet.Helpers
;
using
Breeze.Wallet.Models
;
using
Breeze.Wallet.Models
;
using
NBitcoin
;
namespace
Breeze.Api.Tests
namespace
Breeze.Api.Tests
{
{
...
@@ -16,8 +18,9 @@ namespace Breeze.Api.Tests
...
@@ -16,8 +18,9 @@ namespace Breeze.Api.Tests
[
Fact
]
[
Fact
]
public
void
CreateWalletSuccessfullyReturnsMnemonic
()
public
void
CreateWalletSuccessfullyReturnsMnemonic
()
{
{
var
mockWalletCreate
=
new
Mock
<
IWalletWrapper
>();
Mnemonic
mnemonic
=
new
Mnemonic
(
Wordlist
.
English
,
WordCount
.
Twelve
);
mockWalletCreate
.
Setup
(
wallet
=>
wallet
.
Create
(
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>())).
Returns
(
"mnemonic"
);
var
mockWalletCreate
=
new
Mock
<
IWalletManager
>();
mockWalletCreate
.
Setup
(
wallet
=>
wallet
.
CreateWallet
(
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
null
)).
Returns
(
mnemonic
);
var
controller
=
new
WalletController
(
mockWalletCreate
.
Object
);
var
controller
=
new
WalletController
(
mockWalletCreate
.
Object
);
...
@@ -33,32 +36,31 @@ namespace Breeze.Api.Tests
...
@@ -33,32 +36,31 @@ namespace Breeze.Api.Tests
// Assert
// Assert
mockWalletCreate
.
VerifyAll
();
mockWalletCreate
.
VerifyAll
();
var
viewResult
=
Assert
.
IsType
<
JsonResult
>(
result
);
var
viewResult
=
Assert
.
IsType
<
JsonResult
>(
result
);
Assert
.
Equal
(
"mnemonic"
,
viewResult
.
Value
);
Assert
.
Equal
(
mnemonic
.
ToString
()
,
viewResult
.
Value
);
Assert
.
NotNull
(
result
);
Assert
.
NotNull
(
result
);
}
}
[
Fact
]
[
Fact
]
public
void
LoadWalletSuccessfullyReturnsWalletModel
()
public
void
LoadWalletSuccessfullyReturnsWalletModel
()
{
{
Wallet
Model
walletModel
=
new
WalletModel
Wallet
.
Wallet
wallet
=
new
Wallet
.
Wallet
{
{
FileName
=
"myWallet"
,
Name
=
"myWallet"
,
Network
=
"MainNet"
,
Network
=
WalletHelpers
.
GetNetwork
(
"mainnet"
)
Addresses
=
new
List
<
string
>
{
"address1"
,
"address2"
,
"address3"
,
"address4"
,
"address5"
}
};
};
var
mockWalletWrapper
=
new
Mock
<
IWalletWrapper
>();
mockWalletWrapper
.
Setup
(
wallet
=>
wallet
.
Recover
(
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>())).
Returns
(
walletModel
);
var
mockWalletWrapper
=
new
Mock
<
IWalletManager
>();
mockWalletWrapper
.
Setup
(
w
=>
w
.
RecoverWallet
(
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
null
,
null
)).
Returns
(
wallet
);
var
controller
=
new
WalletController
(
mockWalletWrapper
.
Object
);
var
controller
=
new
WalletController
(
mockWalletWrapper
.
Object
);
// Act
// Act
var
result
=
controller
.
Recover
(
new
WalletRecoveryRequest
var
result
=
controller
.
Recover
(
new
WalletRecoveryRequest
{
{
Name
=
"my
Name
"
,
Name
=
"my
Wallet
"
,
FolderPath
=
""
,
FolderPath
=
""
,
Password
=
""
,
Password
=
""
,
Network
=
""
,
Network
=
"
MainNet
"
,
Mnemonic
=
"mnemonic"
Mnemonic
=
"mnemonic"
});
});
...
@@ -69,28 +71,26 @@ namespace Breeze.Api.Tests
...
@@ -69,28 +71,26 @@ namespace Breeze.Api.Tests
Assert
.
IsType
<
WalletModel
>(
viewResult
.
Value
);
Assert
.
IsType
<
WalletModel
>(
viewResult
.
Value
);
var
model
=
viewResult
.
Value
as
WalletModel
;
var
model
=
viewResult
.
Value
as
WalletModel
;
Assert
.
Equal
(
"
myWallet"
,
model
.
FileName
);
Assert
.
Equal
(
"
Main"
,
model
.
Network
);
}
}
[
Fact
]
[
Fact
]
public
void
RecoverWalletSuccessfullyReturnsWalletModel
()
public
void
RecoverWalletSuccessfullyReturnsWalletModel
()
{
{
Wallet
Model
walletModel
=
new
WalletModel
Wallet
.
Wallet
wallet
=
new
Wallet
.
Wallet
{
{
FileName
=
"myWallet"
,
Name
=
"myWallet"
,
Network
=
"MainNet"
,
Network
=
WalletHelpers
.
GetNetwork
(
"mainnet"
)
Addresses
=
new
List
<
string
>
{
"address1"
,
"address2"
,
"address3"
,
"address4"
,
"address5"
}
};
};
var
mockWalletWrapper
=
new
Mock
<
IWallet
Wrapp
er
>();
var
mockWalletWrapper
=
new
Mock
<
IWallet
Manag
er
>();
mockWalletWrapper
.
Setup
(
w
allet
=>
wallet
.
Load
(
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>())).
Returns
(
walletModel
);
mockWalletWrapper
.
Setup
(
w
=>
w
.
LoadWallet
(
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>())).
Returns
(
wallet
);
var
controller
=
new
WalletController
(
mockWalletWrapper
.
Object
);
var
controller
=
new
WalletController
(
mockWalletWrapper
.
Object
);
// Act
// Act
var
result
=
controller
.
Load
(
new
WalletLoadRequest
var
result
=
controller
.
Load
(
new
WalletLoadRequest
{
{
Name
=
"my
Name
"
,
Name
=
"my
Wallet
"
,
FolderPath
=
""
,
FolderPath
=
""
,
Password
=
""
Password
=
""
});
});
...
@@ -102,14 +102,14 @@ namespace Breeze.Api.Tests
...
@@ -102,14 +102,14 @@ namespace Breeze.Api.Tests
Assert
.
IsType
<
WalletModel
>(
viewResult
.
Value
);
Assert
.
IsType
<
WalletModel
>(
viewResult
.
Value
);
var
model
=
viewResult
.
Value
as
WalletModel
;
var
model
=
viewResult
.
Value
as
WalletModel
;
Assert
.
Equal
(
"
myWallet"
,
model
.
FileName
);
Assert
.
Equal
(
"
Main"
,
model
.
Network
);
}
}
[
Fact
]
[
Fact
]
public
void
FileNotFoundExceptionandReturns404
()
public
void
FileNotFoundExceptionandReturns404
()
{
{
var
mockWalletWrapper
=
new
Mock
<
IWallet
Wrapp
er
>();
var
mockWalletWrapper
=
new
Mock
<
IWallet
Manag
er
>();
mockWalletWrapper
.
Setup
(
wallet
=>
wallet
.
Load
(
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>())).
Throws
<
FileNotFoundException
>();
mockWalletWrapper
.
Setup
(
wallet
=>
wallet
.
Load
Wallet
(
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>(),
It
.
IsAny
<
string
>())).
Throws
<
FileNotFoundException
>();
var
controller
=
new
WalletController
(
mockWalletWrapper
.
Object
);
var
controller
=
new
WalletController
(
mockWalletWrapper
.
Object
);
...
...
Breeze/src/Breeze.Api.Tests/Postman requests/Wallet.postman_collection.json
View file @
b0838544
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
"variables"
:
[],
"variables"
:
[],
"info"
:
{
"info"
:
{
"name"
:
"Wallet"
,
"name"
:
"Wallet"
,
"_postman_id"
:
"
25ac0bc5-23f5-e00d-932f-a8fde7787e7a
"
,
"_postman_id"
:
"
5eec0912-fcf0-50f5-05a2-0835fa13c670
"
,
"description"
:
"Requests relating to operations on the wallet"
,
"description"
:
"Requests relating to operations on the wallet"
,
"schema"
:
"https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
"schema"
:
"https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
},
},
...
@@ -206,6 +206,66 @@
...
@@ -206,6 +206,66 @@
"description"
:
"Gets all the wallets files stored in the default folder"
"description"
:
"Gets all the wallets files stored in the default folder"
},
},
"response"
:
[]
"response"
:
[]
},
{
"name"
:
"New account for non-existant wallet fails"
,
"request"
:
{
"url"
:
"http://localhost:5000/api/v1/wallet/account"
,
"method"
:
"POST"
,
"header"
:
[
{
"key"
:
"Content-Type"
,
"value"
:
"application/json"
,
"description"
:
""
}
],
"body"
:
{
"mode"
:
"raw"
,
"raw"
:
"{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
accountName
\"
:
\"
account one
\"\n
}"
},
"description"
:
""
},
"response"
:
[]
},
{
"name"
:
"Create new account for wallet"
,
"request"
:
{
"url"
:
"http://localhost:5000/api/v1/wallet/account"
,
"method"
:
"POST"
,
"header"
:
[
{
"key"
:
"Content-Type"
,
"value"
:
"application/json"
,
"description"
:
""
}
],
"body"
:
{
"mode"
:
"raw"
,
"raw"
:
"{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
accountName
\"
:
\"
account one
\"
,
\n\t\"
password
\"
:
\"
123456
\"
,
\n\t\"
coinType
\"
: 105
\n
}"
},
"description"
:
""
},
"response"
:
[]
},
{
"name"
:
"Create new address for wallet"
,
"request"
:
{
"url"
:
"http://localhost:5000/api/v1/wallet/address"
,
"method"
:
"POST"
,
"header"
:
[
{
"key"
:
"Content-Type"
,
"value"
:
"application/json"
,
"description"
:
""
}
],
"body"
:
{
"mode"
:
"raw"
,
"raw"
:
"{
\n\t\"
walletName
\"
:
\"
myFirstWallet
\"
,
\n\t\"
accountName
\"
:
\"
account one
\"
,
\n\t\"
coinType
\"
: 0
\n
}"
},
"description"
:
""
},
"response"
:
[]
}
}
]
]
}
}
\ No newline at end of file
Breeze/src/Breeze.Wallet/Breeze.Wallet.csproj
View file @
b0838544
...
@@ -16,6 +16,7 @@
...
@@ -16,6 +16,7 @@
</PropertyGroup>
</PropertyGroup>
<ItemGroup>
<ItemGroup>
<PackageReference Include="ConcurrentHashSet" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="1.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="1.0.3" />
<PackageReference Include="Stratis.Bitcoin" Version="1.0.1.5-alpha" />
<PackageReference Include="Stratis.Bitcoin" Version="1.0.1.5-alpha" />
</ItemGroup>
</ItemGroup>
...
...
Breeze/src/Breeze.Wallet/Controllers/WalletController.cs
View file @
b0838544
...
@@ -8,6 +8,7 @@ using Breeze.Wallet.Errors;
...
@@ -8,6 +8,7 @@ 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
Breeze.Wallet.Wrappers
;
using
NBitcoin
;
namespace
Breeze.Wallet.Controllers
namespace
Breeze.Wallet.Controllers
{
{
...
@@ -17,11 +18,11 @@ namespace Breeze.Wallet.Controllers
...
@@ -17,11 +18,11 @@ namespace Breeze.Wallet.Controllers
[
Route
(
"api/v{version:apiVersion}/[controller]"
)]
[
Route
(
"api/v{version:apiVersion}/[controller]"
)]
public
class
WalletController
:
Controller
public
class
WalletController
:
Controller
{
{
private
readonly
IWallet
Wrapper
walletWrapp
er
;
private
readonly
IWallet
Manager
walletManag
er
;
public
WalletController
(
IWallet
Wrapper
walletWrapp
er
)
public
WalletController
(
IWallet
Manager
walletManag
er
)
{
{
this
.
wallet
Wrapper
=
walletWrapp
er
;
this
.
wallet
Manager
=
walletManag
er
;
}
}
/// <summary>
/// <summary>
...
@@ -44,9 +45,9 @@ namespace Breeze.Wallet.Controllers
...
@@ -44,9 +45,9 @@ namespace Breeze.Wallet.Controllers
{
{
// get the wallet folder
// get the wallet folder
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
Mnemonic
mnemonic
=
this
.
walletManager
.
CreateWallet
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
,
request
.
Network
);
var
mnemonic
=
this
.
walletWrapper
.
Create
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
,
request
.
Network
);
return
this
.
Json
(
mnemonic
.
ToString
());
return
this
.
Json
(
mnemonic
);
}
}
catch
(
InvalidOperationException
e
)
catch
(
InvalidOperationException
e
)
{
{
...
@@ -76,9 +77,13 @@ namespace Breeze.Wallet.Controllers
...
@@ -76,9 +77,13 @@ namespace Breeze.Wallet.Controllers
// get the wallet folder
// get the wallet folder
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
var
wallet
=
this
.
walletWrapper
.
Load
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
);
Wallet
wallet
=
this
.
walletManager
.
LoadWallet
(
request
.
Password
,
walletFolder
.
FullName
,
request
.
Name
);
return
this
.
Json
(
wallet
);
return
this
.
Json
(
new
WalletModel
{
Network
=
wallet
.
Network
.
Name
,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName
=
wallet
.
WalletFilePath
});
}
}
catch
(
FileNotFoundException
e
)
catch
(
FileNotFoundException
e
)
{
{
...
@@ -116,8 +121,13 @@ namespace Breeze.Wallet.Controllers
...
@@ -116,8 +121,13 @@ namespace Breeze.Wallet.Controllers
// get the wallet folder
// get the wallet folder
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
DirectoryInfo
walletFolder
=
GetWalletFolder
(
request
.
FolderPath
);
var
wallet
=
this
.
walletWrapper
.
Recover
(
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
);
return
this
.
Json
(
wallet
);
return
this
.
Json
(
new
WalletModel
{
Network
=
wallet
.
Network
.
Name
,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName
=
wallet
.
WalletFilePath
});
}
}
catch
(
InvalidOperationException
e
)
catch
(
InvalidOperationException
e
)
{
{
...
@@ -153,7 +163,7 @@ namespace Breeze.Wallet.Controllers
...
@@ -153,7 +163,7 @@ namespace Breeze.Wallet.Controllers
try
try
{
{
return
this
.
Json
(
this
.
walletWrapp
er
.
GetGeneralInfo
(
model
.
Name
));
return
this
.
Json
(
this
.
walletManag
er
.
GetGeneralInfo
(
model
.
Name
));
}
}
catch
(
Exception
e
)
catch
(
Exception
e
)
...
@@ -180,7 +190,7 @@ namespace Breeze.Wallet.Controllers
...
@@ -180,7 +190,7 @@ namespace Breeze.Wallet.Controllers
try
try
{
{
return
this
.
Json
(
this
.
walletWrapp
er
.
GetHistory
(
model
.
Name
));
return
this
.
Json
(
this
.
walletManag
er
.
GetHistory
(
model
.
Name
));
}
}
catch
(
Exception
e
)
catch
(
Exception
e
)
...
@@ -207,7 +217,7 @@ namespace Breeze.Wallet.Controllers
...
@@ -207,7 +217,7 @@ namespace Breeze.Wallet.Controllers
try
try
{
{
return
this
.
Json
(
this
.
walletWrapp
er
.
GetBalance
(
model
.
Name
));
return
this
.
Json
(
this
.
walletManag
er
.
GetBalance
(
model
.
Name
));
}
}
catch
(
Exception
e
)
catch
(
Exception
e
)
...
@@ -234,7 +244,7 @@ namespace Breeze.Wallet.Controllers
...
@@ -234,7 +244,7 @@ namespace Breeze.Wallet.Controllers
try
try
{
{
return
this
.
Json
(
this
.
walletWrapp
er
.
BuildTransaction
(
request
.
Password
,
request
.
Address
,
request
.
Amount
,
request
.
FeeType
,
request
.
AllowUnconfirmed
));
return
this
.
Json
(
this
.
walletManag
er
.
BuildTransaction
(
request
.
Password
,
request
.
Address
,
request
.
Amount
,
request
.
FeeType
,
request
.
AllowUnconfirmed
));
}
}
catch
(
Exception
e
)
catch
(
Exception
e
)
...
@@ -261,7 +271,7 @@ namespace Breeze.Wallet.Controllers
...
@@ -261,7 +271,7 @@ namespace Breeze.Wallet.Controllers
try
try
{
{
var
result
=
this
.
walletWrapp
er
.
SendTransaction
(
request
.
Hex
);
var
result
=
this
.
walletManag
er
.
SendTransaction
(
request
.
Hex
);
if
(
result
)
if
(
result
)
{
{
return
this
.
Ok
();
return
this
.
Ok
();
...
@@ -301,6 +311,58 @@ namespace Breeze.Wallet.Controllers
...
@@ -301,6 +311,58 @@ namespace Breeze.Wallet.Controllers
}
}
}
}
/// <summary>
/// Creates a new account for a wallet.
/// </summary>
/// <returns>An account name.</returns>
[
Route
(
"account"
)]
[
HttpPost
]
public
IActionResult
CreateNewAccount
([
FromBody
]
CreateAccountModel
request
)
{
// checks the request is valid
if
(!
this
.
ModelState
.
IsValid
)
{
var
errors
=
this
.
ModelState
.
Values
.
SelectMany
(
e
=>
e
.
Errors
.
Select
(
m
=>
m
.
ErrorMessage
));
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
"Formatting error"
,
string
.
Join
(
Environment
.
NewLine
,
errors
));
}
try
{
var
result
=
this
.
walletManager
.
CreateNewAccount
(
request
.
WalletName
,
request
.
CoinType
,
request
.
AccountName
,
request
.
Password
);
return
this
.
Json
(
result
);
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// Creates a new address for a wallet.
/// </summary>
/// <returns>An address in Base58 format.</returns>
[
Route
(
"address"
)]
[
HttpPost
]
public
IActionResult
CreateNewAddress
([
FromBody
]
CreateAddressModel
request
)
{
// checks the request is valid
if
(!
this
.
ModelState
.
IsValid
)
{
var
errors
=
this
.
ModelState
.
Values
.
SelectMany
(
e
=>
e
.
Errors
.
Select
(
m
=>
m
.
ErrorMessage
));
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
"Formatting error"
,
string
.
Join
(
Environment
.
NewLine
,
errors
));
}
try
{
var
result
=
this
.
walletManager
.
CreateNewAddress
(
request
.
WalletName
,
request
.
CoinType
,
request
.
AccountName
);
return
this
.
Json
(
result
);
}
catch
(
Exception
e
)
{
return
ErrorHelpers
.
BuildErrorResponse
(
HttpStatusCode
.
BadRequest
,
e
.
Message
,
e
.
ToString
());
}
}
/// <summary>
/// <summary>
/// Gets a folder.
/// Gets a folder.
/// </summary>
/// </summary>
...
...
Breeze/src/Breeze.Wallet/IWalletManager.cs
View file @
b0838544
using
System
;
using
System
;
using
Breeze.Wallet.Models
;
using
NBitcoin
;
using
NBitcoin
;
namespace
Breeze.Wallet
namespace
Breeze.Wallet
...
@@ -12,36 +13,72 @@ namespace Breeze.Wallet
...
@@ -12,36 +13,72 @@ namespace Breeze.Wallet
/// Creates a wallet and persist it as a file on the local system.
/// Creates a wallet and persist it as a file on the local system.
/// </summary>
/// </summary>
/// <param name="password">The password used to encrypt sensitive info.</param>
/// <param name="password">The password used to encrypt sensitive info.</param>
/// <param name="
passphrase">The passphrase used in the se
ed.</param>
/// <param name="
folderPath">The folder where the wallet will be sav
ed.</param>
/// <param name="
walletFilePath">The path where the wallet file will be created
.</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="passphrase">The passphrase used in the seed.</param>
/// <returns>A mnemonic defining the wallet's seed used to generate addresses.</returns>
/// <returns>A mnemonic defining the wallet's seed used to generate addresses.</returns>
Mnemonic
CreateWallet
(
string
password
,
string
walletFilePath
,
Network
network
,
string
passphrase
=
null
);
Mnemonic
CreateWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
passphrase
=
null
);
/// <summary>
/// <summary>
/// Loads a wallet from a file.
/// Loads a wallet from a file.
/// </summary>
/// </summary>
/// <param name="password">The password used to encrypt sensitive info.</param>
/// <param name="password">The user's password.</param>
/// <param name="walletFilePath">The location of the wallet file.</param>
/// <param name="folderPath">The folder where the wallet will be loaded.</param>
/// <param name="name">The name of the wallet.</param>
/// <returns>The wallet.</returns>
/// <returns>The wallet.</returns>
Wallet
LoadWallet
(
string
password
,
string
walletFilePath
);
Wallet
LoadWallet
(
string
password
,
string
folderPath
,
string
name
);
/// <summary>
/// <summary>
/// Recovers a wallet.
/// Recovers a wallet.
/// </summary>
/// </summary>
/// <param name="mnemonic">A mnemonic defining the wallet's seed used to generate addresses.</param>
/// <param name="password">The user's password.</param>
/// <param name="password">The password used to encrypt sensitive info.</param>
/// <param name="folderPath">The folder where the wallet will be loaded.</param>
/// <param name="walletFilePath">The location of the wallet file.</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 in which to creae this wallet</param>
/// <param name="mnemonic">The user's mnemonic for the wallet.</param>
/// <param name="passphrase">The passphrase used in the seed.</param>
/// <param name="passphrase">The passphrase used in the seed.</param>
/// <param name="creationTime">The time this wallet was created.</param>
/// <param name="creationTime">The time this wallet was created.</param>
/// <returns>The recovered wallet.</returns>
/// <returns>The recovered wallet.</returns>
Wallet
RecoverWallet
(
Mnemonic
mnemonic
,
string
password
,
string
walletFilePath
,
Network
network
,
string
passphrase
=
null
,
DateTimeOffset
?
creationTime
=
null
);
Wallet
RecoverWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
mnemonic
,
string
passphrase
=
null
,
DateTimeOffset
?
creationTime
=
null
);
/// <summary>
/// <summary>
/// Deleted a wallet.
/// Deleted a wallet.
/// </summary>
/// </summary>
/// <param name="walletFilePath">The location of the wallet file.</param>
/// <param name="walletFilePath">The location of the wallet file.</param>
void
DeleteWallet
(
string
walletFilePath
);
void
DeleteWallet
(
string
walletFilePath
);
/// <summary>
/// Creates a new account.
/// </summary>
/// <param name="walletName">The name of the wallet in which this account will be created.</param>
/// <param name="coinType">the type of coin for which to create an account.</param>
/// <param name="accountName">The name by which this account will be identified.</param>
/// <param name="password">The password used to decrypt the private key.</param>
/// <remarks>
/// According to BIP44, an account at index (i) can only be created when the account
/// at index (i - 1) contains transactions.
/// </remarks>
/// <returns>The name of the new account.</returns>
string
CreateNewAccount
(
string
walletName
,
CoinType
coinType
,
string
accountName
,
string
password
);
/// <summary>
/// Creates the new address.
/// </summary>
/// <param name="walletName">The name of the wallet in which this address will be created.</param>
/// <param name="coinType">the 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>
/// <returns>The new address, in Base58 format.</returns>
string
CreateNewAddress
(
string
walletName
,
CoinType
coinType
,
string
accountName
);
WalletGeneralInfoModel
GetGeneralInfo
(
string
walletName
);
WalletBalanceModel
GetBalance
(
string
walletName
);
WalletHistoryModel
GetHistory
(
string
walletName
);
WalletBuildTransactionModel
BuildTransaction
(
string
password
,
string
address
,
Money
amount
,
string
feeType
,
bool
allowUnconfirmed
);
bool
SendTransaction
(
string
transactionHex
);
}
}
}
}
Breeze/src/Breeze.Wallet/Models/CreateAccountModel.cs
0 → 100644
View file @
b0838544
using
System
;
using
System.Collections.Generic
;
using
System.ComponentModel.DataAnnotations
;
using
System.Text
;
namespace
Breeze.Wallet.Models
{
public
class
CreateAccountModel
{
/// <summary>
/// The name of the wallet in which to create the account.
/// </summary>
[
Required
]
public
string
WalletName
{
get
;
set
;
}
/// <summary>
/// The type of coin this account contains.
/// </summary>
[
Required
]
public
CoinType
CoinType
{
get
;
set
;
}
/// <summary>
/// The name of the account.
/// </summary>
[
Required
]
public
string
AccountName
{
get
;
set
;
}
/// <summary>
/// The password for this wallet.
/// </summary>
[
Required
]
public
string
Password
{
get
;
set
;
}
}
}
Breeze/src/Breeze.Wallet/Models/CreateAddressModel.cs
0 → 100644
View file @
b0838544
using
System
;
using
System.Collections.Generic
;
using
System.ComponentModel.DataAnnotations
;
using
System.Text
;
namespace
Breeze.Wallet.Models
{
public
class
CreateAddressModel
{
/// <summary>
/// The name of the wallet in which to create the address.
/// </summary>
[
Required
]
public
string
WalletName
{
get
;
set
;
}
/// <summary>
/// The type of coin this account contains.
/// </summary>
[
Required
]
public
CoinType
CoinType
{
get
;
set
;
}
/// <summary>
/// The name of the account in which to create the address.
/// </summary>
[
Required
]
public
string
AccountName
{
get
;
set
;
}
}
}
Breeze/src/Breeze.Wallet/Temp/ConcurrentObservableDictionary.cs
0 → 100644
View file @
b0838544
//from https://github.com/brianchance/MonoTouchMVVMCrossValidationTester/blob/master/Validation.Core/ConcurrentObservableDictionary.cs
//modified
using
System
;
using
System.Collections.Concurrent
;
using
System.Linq
;
using
System.ComponentModel
;
using
System.Collections.Generic
;
using
System.Collections.Specialized
;
namespace
System.Collections.ObjectModel
{
public
class
ConcurrentObservableDictionary
<
TKey
,
TValue
>
:
IDictionary
<
TKey
,
TValue
>,
INotifyCollectionChanged
,
INotifyPropertyChanged
{
private
const
string
CountString
=
"Count"
;
private
const
string
IndexerName
=
"Item[]"
;
private
const
string
KeysName
=
"Keys"
;
private
const
string
ValuesName
=
"Values"
;
private
readonly
object
Lock
=
new
object
();
protected
ConcurrentDictionary
<
TKey
,
TValue
>
ConcurrentDictionary
{
get
;
private
set
;
}
#
region
Constructors
public
ConcurrentObservableDictionary
()
{
ConcurrentDictionary
=
new
ConcurrentDictionary
<
TKey
,
TValue
>();
}
public
ConcurrentObservableDictionary
(
ConcurrentDictionary
<
TKey
,
TValue
>
dictionary
)
{
ConcurrentDictionary
=
new
ConcurrentDictionary
<
TKey
,
TValue
>(
dictionary
);
}
public
ConcurrentObservableDictionary
(
IEqualityComparer
<
TKey
>
comparer
)
{
ConcurrentDictionary
=
new
ConcurrentDictionary
<
TKey
,
TValue
>(
comparer
);
}
public
ConcurrentObservableDictionary
(
IDictionary
<
TKey
,
TValue
>
dictionary
,
IEqualityComparer
<
TKey
>
comparer
)
{
ConcurrentDictionary
=
new
ConcurrentDictionary
<
TKey
,
TValue
>(
dictionary
,
comparer
);
}
#
endregion
#
region
IDictionary
<
TKey
,
TValue
>
Members
public
void
Add
(
TKey
key
,
TValue
value
)
=>
Insert
(
key
,
value
,
true
);
public
bool
ContainsKey
(
TKey
key
)
=>
ConcurrentDictionary
.
ContainsKey
(
key
);
public
ICollection
<
TKey
>
Keys
=>
ConcurrentDictionary
.
Keys
;
public
bool
Remove
(
TKey
key
)
=>
Remove
(
key
,
suppressNotifications
:
false
);
private
bool
Remove
(
TKey
key
,
bool
suppressNotifications
)
{
lock
(
Lock
)
{
TValue
value
;
var
ret
=
ConcurrentDictionary
.
TryRemove
(
key
,
out
value
);
if
(
ret
&&
!
suppressNotifications
)
OnCollectionChanged
();
return
ret
;
}
}
public
bool
TryGetValue
(
TKey
key
,
out
TValue
value
)
=>
ConcurrentDictionary
.
TryGetValue
(
key
,
out
value
);
public
ICollection
<
TValue
>
Values
=>
ConcurrentDictionary
.
Values
;
public
TValue
this
[
TKey
key
]
{
get
{
TValue
value
;
return
TryGetValue
(
key
,
out
value
)
?
value
:
default
(
TValue
);
}
set
{
Insert
(
key
,
value
,
false
);
}
}
#
endregion
#
region
ICollection
<
KeyValuePair
<
TKey
,
TValue
>>
Members
public
void
Add
(
KeyValuePair
<
TKey
,
TValue
>
item
)
=>
Insert
(
item
.
Key
,
item
.
Value
,
true
);
public
void
Clear
()
{
lock
(
Lock
)
{
if
(
ConcurrentDictionary
.
Count
>
0
)
{
ConcurrentDictionary
.
Clear
();
OnCollectionChanged
();
}
}
}
public
bool
Contains
(
KeyValuePair
<
TKey
,
TValue
>
item
)
=>
ConcurrentDictionary
.
Contains
(
item
);
/// <summary>
/// NotImplementedException
/// </summary>
/// <param name="array"></param>
/// <param name="arrayIndex"></param>
public
void
CopyTo
(
KeyValuePair
<
TKey
,
TValue
>[]
array
,
int
arrayIndex
)
{
throw
new
NotImplementedException
();
}
/// <summary>
/// NotImplementedException
/// </summary>
public
bool
IsReadOnly
{
get
{
throw
new
NotImplementedException
();
}
}
public
int
Count
=>
ConcurrentDictionary
.
Count
;
public
bool
Remove
(
KeyValuePair
<
TKey
,
TValue
>
item
)
=>
Remove
(
item
.
Key
);
#
endregion
#
region
IEnumerable
<
KeyValuePair
<
TKey
,
TValue
>>
Members
public
IEnumerator
<
KeyValuePair
<
TKey
,
TValue
>>
GetEnumerator
()
=>
ConcurrentDictionary
.
GetEnumerator
();
#
endregion
#
region
IEnumerable
Members
IEnumerator
IEnumerable
.
GetEnumerator
()
=>
((
IEnumerable
)
ConcurrentDictionary
).
GetEnumerator
();
#
endregion
#
region
INotifyCollectionChanged
Members
public
event
NotifyCollectionChangedEventHandler
CollectionChanged
;
#
endregion
#
region
INotifyPropertyChanged
Members
public
event
PropertyChangedEventHandler
PropertyChanged
;
#
endregion
public
void
AddOrReplace
(
TKey
key
,
TValue
value
)
{
if
(
ContainsKey
(
key
))
{
Remove
(
key
,
suppressNotifications
:
true
);
Add
(
key
,
value
);
}
else
{
Add
(
key
,
value
);
}
}
/// <summary>
/// NotImplementedException
/// </summary>
/// <param name="items"></param>
public
void
AddRange
(
IDictionary
<
TKey
,
TValue
>
items
)
{
throw
new
NotImplementedException
();
}
private
void
Insert
(
TKey
key
,
TValue
value
,
bool
add
)
{
lock
(
Lock
)
{
if
(
key
==
null
)
throw
new
ArgumentNullException
(
nameof
(
key
));
TValue
item
;
if
(
ConcurrentDictionary
.
TryGetValue
(
key
,
out
item
))
{
if
(
add
)
throw
new
ArgumentException
(
"An item with the same key has already been added."
);
if
(
Equals
(
item
,
value
))
return
;
ConcurrentDictionary
[
key
]
=
value
;
OnCollectionChanged
(
NotifyCollectionChangedAction
.
Replace
,
new
KeyValuePair
<
TKey
,
TValue
>(
key
,
value
),
new
KeyValuePair
<
TKey
,
TValue
>(
key
,
item
));
OnPropertyChanged
(
key
.
ToString
());
}
else
{
ConcurrentDictionary
[
key
]
=
value
;
OnCollectionChanged
(
NotifyCollectionChangedAction
.
Add
,
new
KeyValuePair
<
TKey
,
TValue
>(
key
,
value
));
OnPropertyChanged
(
key
.
ToString
());
}
}
}
private
void
OnPropertyChanged
()
{
OnPropertyChanged
(
CountString
);
OnPropertyChanged
(
IndexerName
);
OnPropertyChanged
(
KeysName
);
OnPropertyChanged
(
ValuesName
);
}
protected
virtual
void
OnPropertyChanged
(
string
propertyName
)
{
PropertyChanged
?.
Invoke
(
this
,
new
PropertyChangedEventArgs
(
propertyName
));
}
private
void
OnCollectionChanged
()
{
OnPropertyChanged
();
CollectionChanged
?.
Invoke
(
this
,
new
NotifyCollectionChangedEventArgs
(
NotifyCollectionChangedAction
.
Reset
));
}
private
void
OnCollectionChanged
(
NotifyCollectionChangedAction
action
,
KeyValuePair
<
TKey
,
TValue
>
changedItem
)
{
OnPropertyChanged
();
CollectionChanged
?.
Invoke
(
this
,
new
NotifyCollectionChangedEventArgs
(
action
,
changedItem
,
0
));
}
private
void
OnCollectionChanged
(
NotifyCollectionChangedAction
action
,
KeyValuePair
<
TKey
,
TValue
>
newItem
,
KeyValuePair
<
TKey
,
TValue
>
oldItem
)
{
OnPropertyChanged
();
CollectionChanged
?.
Invoke
(
this
,
new
NotifyCollectionChangedEventArgs
(
action
,
newItem
,
oldItem
,
0
));
}
private
void
OnCollectionChanged
(
NotifyCollectionChangedAction
action
,
IList
newItems
)
{
OnPropertyChanged
();
CollectionChanged
?.
Invoke
(
this
,
new
NotifyCollectionChangedEventArgs
(
action
,
newItems
,
0
));
}
}
}
\ No newline at end of file
Breeze/src/Breeze.Wallet/Temp/ConcurrentObservableHashSet.cs
0 → 100644
View file @
b0838544
using
System
;
using
System.Collections
;
using
System.Collections.Concurrent
;
using
System.Linq
;
using
System.ComponentModel
;
using
System.Collections.Generic
;
using
System.Collections.Specialized
;
using
ConcurrentCollections
;
namespace
System.Collections.ObjectModel
{
public
class
ConcurrentObservableHashSet
<
T
>
:
INotifyCollectionChanged
,
IReadOnlyCollection
<
T
>
{
protected
ConcurrentHashSet
<
T
>
ConcurrentHashSet
{
get
;
}
private
readonly
object
Lock
=
new
object
();
public
ConcurrentObservableHashSet
()
{
ConcurrentHashSet
=
new
ConcurrentHashSet
<
T
>();
}
public
event
NotifyCollectionChangedEventHandler
CollectionChanged
;
private
void
OnCollectionChanged
()
{
CollectionChanged
?.
Invoke
(
this
,
new
NotifyCollectionChangedEventArgs
(
NotifyCollectionChangedAction
.
Reset
));
}
IEnumerator
IEnumerable
.
GetEnumerator
()
=>
GetEnumerator
();
public
IEnumerator
<
T
>
GetEnumerator
()
=>
ConcurrentHashSet
.
GetEnumerator
();
public
bool
TryAdd
(
T
item
)
{
lock
(
Lock
)
{
if
(
ConcurrentHashSet
.
Add
(
item
))
{
OnCollectionChanged
();
return
true
;
}
return
false
;
}
}
public
void
Clear
()
{
lock
(
Lock
)
{
if
(
ConcurrentHashSet
.
Count
>
0
)
{
ConcurrentHashSet
.
Clear
();
OnCollectionChanged
();
}
}
}
public
bool
Contains
(
T
item
)
=>
ConcurrentHashSet
.
Contains
(
item
);
public
bool
TryRemove
(
T
item
)
{
lock
(
Lock
)
{
if
(
ConcurrentHashSet
.
TryRemove
(
item
))
{
OnCollectionChanged
();
return
true
;
}
return
false
;
}
}
public
int
Count
=>
ConcurrentHashSet
.
Count
;
}
}
Breeze/src/Breeze.Wallet/Temp/Height.cs
0 → 100644
View file @
b0838544
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Threading.Tasks
;
namespace
HBitcoin.Models
{
public
struct
Height
:
IEquatable
<
Height
>,
IEquatable
<
int
>,
IComparable
<
Height
>,
IComparable
<
int
>
{
public
HeightType
Type
{
get
;
}
private
readonly
int
_value
;
public
int
Value
{
get
{
if
(
Type
==
HeightType
.
Chain
)
return
_value
;
if
(
Type
==
HeightType
.
MemPool
)
return
int
.
MaxValue
-
1
;
//if(Type == HeightType.Unknown)
return
int
.
MaxValue
;
}
}
public
static
Height
MemPool
=>
new
Height
(
HeightType
.
MemPool
);
public
static
Height
Unknown
=>
new
Height
(
HeightType
.
Unknown
);
public
Height
(
int
height
)
{
if
(
height
<
0
)
throw
new
ArgumentException
(
$"
{
nameof
(
height
)}
:
{
height
}
cannot be < 0"
);
if
(
height
==
int
.
MaxValue
)
Type
=
HeightType
.
Unknown
;
else
if
(
height
==
int
.
MaxValue
-
1
)
Type
=
HeightType
.
MemPool
;
else
Type
=
HeightType
.
Chain
;
_value
=
height
;
}
public
Height
(
string
heightOrHeightType
)
{
var
trimmed
=
heightOrHeightType
.
Trim
();
if
(
trimmed
==
HeightType
.
MemPool
.
ToString
())
this
=
MemPool
;
else
if
(
trimmed
==
HeightType
.
Unknown
.
ToString
())
this
=
Unknown
;
else
this
=
new
Height
(
int
.
Parse
(
trimmed
));
}
public
Height
(
HeightType
type
)
{
if
(
type
==
HeightType
.
Chain
)
throw
new
NotSupportedException
(
$"For
{
type
}
height must be specified"
);
Type
=
type
;
if
(
Type
==
HeightType
.
MemPool
)
_value
=
int
.
MaxValue
-
1
;
else
_value
=
int
.
MaxValue
;
// HeightType.Unknown
}
public
override
string
ToString
()
{
if
(
Type
==
HeightType
.
Chain
)
return
Value
.
ToString
();
else
return
Type
.
ToString
();
}
#
region
EqualityAndComparison
public
override
bool
Equals
(
object
obj
)
=>
obj
is
Height
&&
this
==
(
Height
)
obj
;
public
bool
Equals
(
Height
other
)
=>
this
==
other
;
public
override
int
GetHashCode
()
=>
Value
.
GetHashCode
();
public
static
bool
operator
==(
Height
x
,
Height
y
)
=>
x
.
Value
==
y
.
Value
;
public
static
bool
operator
!=(
Height
x
,
Height
y
)
=>
!(
x
==
y
);
public
bool
Equals
(
int
other
)
=>
Value
==
other
;
public
static
bool
operator
==(
int
x
,
Height
y
)
=>
x
==
y
.
Value
;
public
static
bool
operator
==(
Height
x
,
int
y
)
=>
x
.
Value
==
y
;
public
static
bool
operator
!=(
int
x
,
Height
y
)
=>
!(
x
==
y
);
public
static
bool
operator
!=(
Height
x
,
int
y
)
=>
!(
x
==
y
);
public
int
CompareTo
(
Height
other
)
=>
Value
.
CompareTo
(
other
.
Value
);
public
int
CompareTo
(
int
other
)
{
if
(
Value
>
other
)
return
-
1
;
if
(
Value
==
other
)
return
0
;
return
1
;
}
public
static
bool
operator
>(
Height
x
,
Height
y
)
=>
x
.
Value
>
y
.
Value
;
public
static
bool
operator
<(
Height
x
,
Height
y
)
=>
x
.
Value
<
y
.
Value
;
public
static
bool
operator
>=(
Height
x
,
Height
y
)
=>
x
.
Value
>=
y
.
Value
;
public
static
bool
operator
<=(
Height
x
,
Height
y
)
=>
x
.
Value
<=
y
.
Value
;
public
static
bool
operator
>(
int
x
,
Height
y
)
=>
x
>
y
.
Value
;
public
static
bool
operator
>(
Height
x
,
int
y
)
=>
x
.
Value
>
y
;
public
static
bool
operator
<(
int
x
,
Height
y
)
=>
x
<
y
.
Value
;
public
static
bool
operator
<(
Height
x
,
int
y
)
=>
x
.
Value
<
y
;
public
static
bool
operator
>=(
int
x
,
Height
y
)
=>
x
>=
y
.
Value
;
public
static
bool
operator
<=(
int
x
,
Height
y
)
=>
x
<=
y
.
Value
;
public
static
bool
operator
>=(
Height
x
,
int
y
)
=>
x
.
Value
>=
y
;
public
static
bool
operator
<=(
Height
x
,
int
y
)
=>
x
.
Value
<=
y
;
#
endregion
}
public
enum
HeightType
{
Chain
,
MemPool
,
Unknown
}
}
Breeze/src/Breeze.Wallet/Temp/SmartMerkleBlock.cs
0 → 100644
View file @
b0838544
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
ConcurrentCollections
;
using
HBitcoin.Models
;
using
NBitcoin
;
namespace
HBitcoin.Models
{
public
class
SmartMerkleBlock
:
IEquatable
<
SmartMerkleBlock
>,
IComparable
<
SmartMerkleBlock
>
{
#
region
Members
public
Height
Height
{
get
;
}
public
MerkleBlock
MerkleBlock
{
get
;
}
public
IEnumerable
<
uint256
>
GetMatchedTransactions
()
=>
MerkleBlock
.
PartialMerkleTree
.
GetMatchedTransactions
();
public
uint
TransactionCount
=>
MerkleBlock
.
PartialMerkleTree
.
TransactionCount
;
#
endregion
#
region
Constructors
public
SmartMerkleBlock
()
{
}
public
SmartMerkleBlock
(
Height
height
,
Block
block
,
params
uint256
[]
interestedTransactionIds
)
{
Height
=
height
;
MerkleBlock
=
interestedTransactionIds
==
null
||
interestedTransactionIds
.
Length
==
0
?
block
.
Filter
()
:
block
.
Filter
(
interestedTransactionIds
);
}
public
SmartMerkleBlock
(
int
height
,
Block
block
,
params
uint256
[]
interestedTransactionIds
)
{
Height
=
new
Height
(
height
);
MerkleBlock
=
interestedTransactionIds
==
null
||
interestedTransactionIds
.
Length
==
0
?
block
.
Filter
()
:
block
.
Filter
(
interestedTransactionIds
);
}
public
SmartMerkleBlock
(
Height
height
,
MerkleBlock
merkleBlock
)
{
Height
=
height
;
MerkleBlock
=
merkleBlock
;
}
#
endregion
#
region
Formatting
public
static
byte
[]
ToBytes
(
SmartMerkleBlock
smartMerkleBlock
)
=>
BitConverter
.
GetBytes
(
smartMerkleBlock
.
Height
.
Value
)
// 4bytes
.
Concat
(
smartMerkleBlock
.
MerkleBlock
.
ToBytes
())
.
ToArray
();
public
byte
[]
ToBytes
()
=>
ToBytes
(
this
);
public
static
SmartMerkleBlock
FromBytes
(
byte
[]
bytes
)
{
var
heightBytes
=
bytes
.
Take
(
4
).
ToArray
();
var
merkleBlockBytes
=
bytes
.
Skip
(
4
).
ToArray
();
var
height
=
new
Height
(
BitConverter
.
ToInt32
(
heightBytes
,
startIndex
:
0
));
// Bypass NBitcoin bug
var
merkleBlock
=
new
MerkleBlock
();
if
(!
merkleBlock
.
ToBytes
().
SequenceEqual
(
merkleBlockBytes
))
// if not default MerkleBlock
{
merkleBlock
.
FromBytes
(
merkleBlockBytes
);
}
return
new
SmartMerkleBlock
(
height
,
merkleBlock
);
}
#
endregion
#
region
EqualityAndComparison
public
override
bool
Equals
(
object
obj
)
=>
obj
is
SmartMerkleBlock
&&
this
==
(
SmartMerkleBlock
)
obj
;
public
bool
Equals
(
SmartMerkleBlock
other
)
=>
this
==
other
;
public
override
int
GetHashCode
()
{
var
hash
=
Height
.
GetHashCode
();
hash
=
hash
^
MerkleBlock
.
Header
.
GetHash
().
GetHashCode
();
hash
=
hash
^
MerkleBlock
.
Header
.
HashPrevBlock
.
GetHashCode
();
hash
=
hash
^
MerkleBlock
.
Header
.
HashMerkleRoot
.
GetHashCode
();
foreach
(
uint256
txhash
in
GetMatchedTransactions
())
hash
=
hash
^
txhash
.
GetHashCode
();
return
hash
;
}
public
static
bool
operator
==(
SmartMerkleBlock
x
,
SmartMerkleBlock
y
)
{
if
(
x
.
Height
!=
y
.
Height
)
return
false
;
if
(
x
.
MerkleBlock
.
Header
.
GetHash
()
!=
y
.
MerkleBlock
.
Header
.
GetHash
())
return
false
;
if
(
x
.
MerkleBlock
.
Header
.
HashPrevBlock
!=
y
.
MerkleBlock
.
Header
.
HashPrevBlock
)
return
false
;
if
(
x
.
MerkleBlock
.
Header
.
HashMerkleRoot
!=
y
.
MerkleBlock
.
Header
.
HashMerkleRoot
)
return
false
;
if
(
x
.
TransactionCount
!=
y
.
TransactionCount
)
return
false
;
if
(
x
.
TransactionCount
==
0
)
return
true
;
if
(!
x
.
GetMatchedTransactions
().
SequenceEqual
(
y
.
GetMatchedTransactions
()))
return
false
;
return
true
;
}
public
static
bool
operator
!=(
SmartMerkleBlock
x
,
SmartMerkleBlock
y
)
=>
!(
x
==
y
);
public
int
CompareTo
(
SmartMerkleBlock
other
)
=>
Height
.
CompareTo
(
other
.
Height
);
public
static
bool
operator
>(
SmartMerkleBlock
x
,
SmartMerkleBlock
y
)
=>
x
.
Height
>
y
.
Height
;
public
static
bool
operator
<(
SmartMerkleBlock
x
,
SmartMerkleBlock
y
)
=>
x
.
Height
<
y
.
Height
;
public
static
bool
operator
>=(
SmartMerkleBlock
x
,
SmartMerkleBlock
y
)
=>
x
.
Height
>=
y
.
Height
;
public
static
bool
operator
<=(
SmartMerkleBlock
x
,
SmartMerkleBlock
y
)
=>
x
.
Height
<=
y
.
Height
;
#
endregion
}
}
Breeze/src/Breeze.Wallet/Temp/SmartTransaction.cs
0 → 100644
View file @
b0838544
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Threading.Tasks
;
using
HBitcoin.Models
;
using
NBitcoin
;
namespace
HBitcoin.Models
{
public
class
SmartTransaction
:
IEquatable
<
SmartTransaction
>
{
#
region
Members
public
Height
Height
{
get
;
}
public
Transaction
Transaction
{
get
;
}
public
bool
Confirmed
=>
Height
.
Type
==
HeightType
.
Chain
;
public
uint256
GetHash
()
=>
Transaction
.
GetHash
();
#
endregion
#
region
Constructors
public
SmartTransaction
()
{
}
public
SmartTransaction
(
Transaction
transaction
,
Height
height
)
{
Height
=
height
;
Transaction
=
transaction
;
}
#
endregion
#
region
Equality
public
bool
Equals
(
SmartTransaction
other
)
=>
GetHash
().
Equals
(
other
.
GetHash
());
public
bool
Equals
(
Transaction
other
)
=>
GetHash
().
Equals
(
other
.
GetHash
());
public
override
bool
Equals
(
object
obj
)
{
bool
rc
=
false
;
if
(
obj
is
SmartTransaction
)
{
var
transaction
=
(
SmartTransaction
)
obj
;
rc
=
GetHash
().
Equals
(
transaction
.
GetHash
());
}
else
if
(
obj
is
Transaction
)
{
var
transaction
=
(
Transaction
)
obj
;
rc
=
GetHash
().
Equals
(
transaction
.
GetHash
());
}
return
rc
;
}
public
override
int
GetHashCode
()
{
return
GetHash
().
GetHashCode
();
}
public
static
bool
operator
!=(
SmartTransaction
tx1
,
SmartTransaction
tx2
)
{
return
!(
tx1
==
tx2
);
}
public
static
bool
operator
==(
SmartTransaction
tx1
,
SmartTransaction
tx2
)
{
bool
rc
;
if
(
ReferenceEquals
(
tx1
,
tx2
))
rc
=
true
;
else
if
((
object
)
tx1
==
null
||
(
object
)
tx2
==
null
)
{
rc
=
false
;
}
else
{
rc
=
tx1
.
GetHash
().
Equals
(
tx2
.
GetHash
());
}
return
rc
;
}
public
static
bool
operator
==(
Transaction
tx1
,
SmartTransaction
tx2
)
{
bool
rc
;
if
((
object
)
tx1
==
null
||
(
object
)
tx2
==
null
)
{
rc
=
false
;
}
else
{
rc
=
tx1
.
GetHash
().
Equals
(
tx2
.
GetHash
());
}
return
rc
;
}
public
static
bool
operator
!=(
Transaction
tx1
,
SmartTransaction
tx2
)
{
return
!(
tx1
==
tx2
);
}
public
static
bool
operator
==(
SmartTransaction
tx1
,
Transaction
tx2
)
{
bool
rc
;
if
((
object
)
tx1
==
null
||
(
object
)
tx2
==
null
)
{
rc
=
false
;
}
else
{
rc
=
tx1
.
GetHash
().
Equals
(
tx2
.
GetHash
());
}
return
rc
;
}
public
static
bool
operator
!=(
SmartTransaction
tx1
,
Transaction
tx2
)
{
return
!(
tx1
==
tx2
);
}
#
endregion
}
}
Breeze/src/Breeze.Wallet/Temp/Tracker.cs
0 → 100644
View file @
b0838544
using
System
;
using
System.Collections.Concurrent
;
using
System.Collections.Generic
;
using
System.Collections.ObjectModel
;
using
System.Diagnostics
;
using
System.IO
;
using
System.Linq
;
using
System.Threading
;
using
System.Threading.Tasks
;
using
ConcurrentCollections
;
using
HBitcoin.Models
;
using
NBitcoin
;
namespace
HBitcoin.FullBlockSpv
{
public
class
Tracker
{
#
region
Members
public
Network
Network
{
get
;
private
set
;
}
public
ConcurrentHashSet
<
SmartMerkleBlock
>
MerkleChain
{
get
;
}
=
new
ConcurrentHashSet
<
SmartMerkleBlock
>();
/// <summary>
///
/// </summary>
/// <param name="scriptPubKey"></param>
/// <param name="receivedTransactions">int: block height</param>
/// <param name="spentTransactions">int: block height</param>
/// <returns></returns>
public
bool
TryFindConfirmedTransactions
(
Script
scriptPubKey
,
out
ConcurrentHashSet
<
SmartTransaction
>
receivedTransactions
,
out
ConcurrentHashSet
<
SmartTransaction
>
spentTransactions
)
{
var
found
=
false
;
receivedTransactions
=
new
ConcurrentHashSet
<
SmartTransaction
>();
spentTransactions
=
new
ConcurrentHashSet
<
SmartTransaction
>();
foreach
(
var
tx
in
TrackedTransactions
.
Where
(
x
=>
x
.
Confirmed
))
{
// if already has that tx continue
if
(
receivedTransactions
.
Any
(
x
=>
x
.
GetHash
()
==
tx
.
GetHash
()))
continue
;
foreach
(
var
output
in
tx
.
Transaction
.
Outputs
)
{
if
(
output
.
ScriptPubKey
.
Equals
(
scriptPubKey
))
{
receivedTransactions
.
Add
(
tx
);
found
=
true
;
}
}
}
if
(
found
)
{
foreach
(
var
tx
in
TrackedTransactions
.
Where
(
x
=>
x
.
Confirmed
))
{
// if already has that tx continue
if
(
spentTransactions
.
Any
(
x
=>
x
.
GetHash
()
==
tx
.
GetHash
()))
continue
;
foreach
(
var
input
in
tx
.
Transaction
.
Inputs
)
{
if
(
receivedTransactions
.
Select
(
x
=>
x
.
GetHash
()).
Contains
(
input
.
PrevOut
.
Hash
))
{
spentTransactions
.
Add
(
tx
);
found
=
true
;
}
}
}
}
return
found
;
}
/// <summary>
///
/// </summary>
/// <param name="scriptPubKey"></param>
/// <returns>if never had any money on it</returns>
public
bool
IsClean
(
Script
scriptPubKey
)
=>
TrackedTransactions
.
All
(
tx
=>
!
tx
.
Transaction
.
Outputs
.
Any
(
output
=>
output
.
ScriptPubKey
.
Equals
(
scriptPubKey
)));
public
ConcurrentObservableHashSet
<
SmartTransaction
>
TrackedTransactions
{
get
;
}
=
new
ConcurrentObservableHashSet
<
SmartTransaction
>();
public
ConcurrentHashSet
<
Script
>
TrackedScriptPubKeys
{
get
;
}
=
new
ConcurrentHashSet
<
Script
>();
public
readonly
UnprocessedBlockBuffer
UnprocessedBlockBuffer
=
new
UnprocessedBlockBuffer
();
private
Height
_bestHeight
=
Height
.
Unknown
;
public
Height
BestHeight
{
get
{
return
_bestHeight
;
}
private
set
{
if
(
_bestHeight
==
value
)
return
;
_bestHeight
=
value
;
OnBestHeightChanged
();
}
}
public
event
EventHandler
BestHeightChanged
;
private
void
OnBestHeightChanged
()
=>
BestHeightChanged
?.
Invoke
(
this
,
EventArgs
.
Empty
);
public
int
BlockCount
=>
MerkleChain
.
Count
;
#
endregion
#
region
Constructors
private
Tracker
()
{
}
public
Tracker
(
Network
network
)
{
Network
=
network
;
UnprocessedBlockBuffer
.
HaveBlocks
+=
UnprocessedBlockBuffer_HaveBlocks
;
}
#
endregion
#
region
Tracking
private
IEnumerable
<
SmartTransaction
>
GetNotYetFoundTrackedTransactions
()
{
var
notFound
=
new
HashSet
<
SmartTransaction
>();
foreach
(
var
tx
in
TrackedTransactions
.
Where
(
x
=>
!
x
.
Confirmed
))
{
notFound
.
Add
(
tx
);
}
return
notFound
;
}
#
endregion
public
void
ReorgOne
()
{
// remove the last block
if
(
MerkleChain
.
Count
!=
0
)
{
if
(
MerkleChain
.
TryRemove
(
MerkleChain
.
Max
()))
{
BestHeight
=
MerkleChain
.
Max
().
Height
;
}
}
}
public
void
AddOrReplaceBlock
(
Height
height
,
Block
block
)
{
UnprocessedBlockBuffer
.
TryAddOrReplace
(
height
,
block
);
}
#
region
TransactionProcessing
/// <returns>if processed it transaction</returns>
public
bool
ProcessTransaction
(
SmartTransaction
transaction
)
{
// 1. If already tracking can we update it?
var
found
=
TrackedTransactions
.
FirstOrDefault
(
x
=>
x
==
transaction
);
if
(
found
!=
default
(
SmartTransaction
))
{
// if in a lower level don't track
if
(
found
.
Height
.
Type
<=
transaction
.
Height
.
Type
)
return
false
;
else
{
// else update
TrackedTransactions
.
TryRemove
(
transaction
);
TrackedTransactions
.
TryAdd
(
transaction
);
return
true
;
}
}
// 2. If this transaction arrived to any of our scriptpubkey track it!
if
(
transaction
.
Transaction
.
Outputs
.
Any
(
output
=>
TrackedScriptPubKeys
.
Contains
(
output
.
ScriptPubKey
)))
{
TrackedTransactions
.
TryAdd
(
transaction
);
return
true
;
}
// 3. If this transaction spends any of our scriptpubkeys track it!
if
(
transaction
.
Transaction
.
Inputs
.
Any
(
input
=>
TrackedTransactions
.
Where
(
ttx
=>
ttx
.
GetHash
()
==
input
.
PrevOut
.
Hash
)
.
Any
(
ttx
=>
TrackedScriptPubKeys
.
Contains
(
ttx
.
Transaction
.
Outputs
[
input
.
PrevOut
.
N
].
ScriptPubKey
))))
{
TrackedTransactions
.
TryAdd
(
transaction
);
return
true
;
}
// if got so far we are not interested
return
false
;
}
/// <returns>transactions it processed, empty if not processed any</returns>
private
HashSet
<
SmartTransaction
>
ProcessTransactions
(
IEnumerable
<
Transaction
>
transactions
,
Height
height
)
{
var
processedTransactions
=
new
HashSet
<
SmartTransaction
>();
try
{
// Process all transactions
foreach
(
var
tx
in
transactions
)
{
var
smartTx
=
new
SmartTransaction
(
tx
,
height
);
if
(
ProcessTransaction
(
smartTx
))
{
processedTransactions
.
Add
(
smartTx
);
}
}
// If processed any then do it again recursively until zero new is processed
if
(
processedTransactions
.
Count
>
0
)
{
var
newlyProcessedTransactions
=
ProcessTransactions
(
transactions
,
height
);
foreach
(
var
ptx
in
newlyProcessedTransactions
)
{
processedTransactions
.
Add
(
ptx
);
}
}
}
catch
(
Exception
ex
)
{
Debug
.
WriteLine
(
$"Ignoring
{
nameof
(
ProcessBlock
)}
exception at height
{
height
}
:"
);
Debug
.
WriteLine
(
ex
);
}
return
processedTransactions
;
}
/// <returns>transactions it processed, empty if not processed any</returns>
private
HashSet
<
SmartTransaction
>
ProcessBlock
(
Height
height
,
Block
block
)
{
var
foundTransactions
=
ProcessTransactions
(
block
.
Transactions
,
height
);
var
smartMerkleBlock
=
new
SmartMerkleBlock
(
height
,
block
,
foundTransactions
.
Select
(
x
=>
x
.
GetHash
()).
ToArray
());
var
sameHeights
=
MerkleChain
.
Where
(
x
=>
x
.
Height
==
height
);
foreach
(
var
elem
in
sameHeights
)
{
MerkleChain
.
TryRemove
(
elem
);
}
MerkleChain
.
Add
(
smartMerkleBlock
);
BestHeight
=
height
;
return
foundTransactions
;
}
#
endregion
private
void
UnprocessedBlockBuffer_HaveBlocks
(
object
sender
,
EventArgs
e
)
{
Height
height
;
Block
block
;
while
(
UnprocessedBlockBuffer
.
TryGetAndRemoveOldest
(
out
height
,
out
block
))
{
ProcessBlock
(
height
,
block
);
}
}
#
region
Saving
private
readonly
SemaphoreSlim
Saving
=
new
SemaphoreSlim
(
1
,
1
);
private
const
string
TrackedScriptPubKeysFileName
=
"TrackedScriptPubKeys.dat"
;
private
const
string
TrackedTransactionsFileName
=
"TrackedTransactions.dat"
;
private
const
string
MerkleChainFileName
=
"MerkleChain.dat"
;
private
static
readonly
byte
[]
blockSep
=
new
byte
[]
{
0x10
,
0x1A
,
0x7B
,
0x23
,
0x5D
,
0x12
,
0x7D
};
public
async
Task
SaveAsync
(
string
trackerFolderPath
)
{
await
Saving
.
WaitAsync
().
ConfigureAwait
(
false
);
try
{
if
(
TrackedScriptPubKeys
.
Count
>
0
||
TrackedTransactions
.
Count
>
0
||
MerkleChain
.
Count
>
0
)
{
Directory
.
CreateDirectory
(
trackerFolderPath
);
}
if
(
TrackedScriptPubKeys
.
Count
>
0
)
{
File
.
WriteAllLines
(
Path
.
Combine
(
trackerFolderPath
,
TrackedScriptPubKeysFileName
),
TrackedScriptPubKeys
.
Select
(
x
=>
x
.
ToString
()));
}
if
(
TrackedTransactions
.
Count
>
0
)
{
File
.
WriteAllLines
(
Path
.
Combine
(
trackerFolderPath
,
TrackedTransactionsFileName
),
TrackedTransactions
.
Select
(
x
=>
$"
{
x
.
Transaction
.
ToHex
()}
:
{
x
.
Height
}
"
));
}
if
(
MerkleChain
.
Count
>
0
)
{
var
path
=
Path
.
Combine
(
trackerFolderPath
,
MerkleChainFileName
);
if
(
File
.
Exists
(
path
))
{
const
string
backupName
=
MerkleChainFileName
+
"_backup"
;
var
backupPath
=
Path
.
Combine
(
trackerFolderPath
,
backupName
);
File
.
Copy
(
path
,
backupPath
,
overwrite
:
true
);
File
.
Delete
(
path
);
}
using
(
FileStream
stream
=
File
.
OpenWrite
(
path
))
{
var
toFile
=
MerkleChain
.
First
().
ToBytes
();
await
stream
.
WriteAsync
(
toFile
,
0
,
toFile
.
Length
).
ConfigureAwait
(
false
);
foreach
(
var
block
in
MerkleChain
.
Skip
(
1
))
{
await
stream
.
WriteAsync
(
blockSep
,
0
,
blockSep
.
Length
).
ConfigureAwait
(
false
);
var
blockBytes
=
block
.
ToBytes
();
await
stream
.
WriteAsync
(
blockBytes
,
0
,
blockBytes
.
Length
).
ConfigureAwait
(
false
);
}
}
}
}
finally
{
Saving
.
Release
();
}
}
public
async
Task
LoadAsync
(
string
trackerFolderPath
)
{
await
Saving
.
WaitAsync
().
ConfigureAwait
(
false
);
try
{
if
(!
Directory
.
Exists
(
trackerFolderPath
))
throw
new
DirectoryNotFoundException
(
$"No Blockchain found at
{
trackerFolderPath
}
"
);
var
tspb
=
Path
.
Combine
(
trackerFolderPath
,
TrackedScriptPubKeysFileName
);
if
(
File
.
Exists
(
tspb
)
&&
new
FileInfo
(
tspb
).
Length
!=
0
)
{
foreach
(
var
line
in
File
.
ReadAllLines
(
tspb
))
{
TrackedScriptPubKeys
.
Add
(
new
Script
(
line
));
}
}
var
tt
=
Path
.
Combine
(
trackerFolderPath
,
TrackedTransactionsFileName
);
if
(
File
.
Exists
(
tt
)
&&
new
FileInfo
(
tt
).
Length
!=
0
)
{
foreach
(
var
line
in
File
.
ReadAllLines
(
tt
))
{
var
pieces
=
line
.
Split
(
':'
);
ProcessTransaction
(
new
SmartTransaction
(
new
Transaction
(
pieces
[
0
]),
new
Height
(
pieces
[
1
])));
}
}
var
pbc
=
Path
.
Combine
(
trackerFolderPath
,
MerkleChainFileName
);
if
(
File
.
Exists
(
pbc
)
&&
new
FileInfo
(
pbc
).
Length
!=
0
)
{
foreach
(
var
block
in
Util
.
Separate
(
File
.
ReadAllBytes
(
pbc
),
blockSep
))
{
SmartMerkleBlock
smartMerkleBlock
=
SmartMerkleBlock
.
FromBytes
(
block
);
MerkleChain
.
Add
(
smartMerkleBlock
);
}
BestHeight
=
MerkleChain
.
Max
().
Height
;
}
}
finally
{
Saving
.
Release
();
}
}
#
endregion
}
}
Breeze/src/Breeze.Wallet/Temp/UnprocessedBlockBuffer.cs
0 → 100644
View file @
b0838544
using
System
;
using
System.Collections.Concurrent
;
using
System.Collections.Generic
;
using
System.Collections.ObjectModel
;
using
System.Linq
;
using
System.Threading.Tasks
;
using
HBitcoin.Models
;
using
NBitcoin
;
namespace
HBitcoin.FullBlockSpv
{
public
class
UnprocessedBlockBuffer
{
public
const
int
Capacity
=
50
;
private
readonly
ConcurrentObservableDictionary
<
Height
,
Block
>
_blocks
=
new
ConcurrentObservableDictionary
<
Height
,
Block
>();
public
event
EventHandler
HaveBlocks
;
private
void
OnHaveBlocks
()
=>
HaveBlocks
?.
Invoke
(
this
,
EventArgs
.
Empty
);
/// <summary>
///
/// </summary>
/// <param name="height"></param>
/// <param name="block"></param>
/// <returns>false if we have more than UnprocessedBlockBuffer.Capacity blocks in memory already</returns>
public
bool
TryAddOrReplace
(
Height
height
,
Block
block
)
{
if
(
_blocks
.
Count
>
Capacity
)
return
false
;
_blocks
.
AddOrReplace
(
height
,
block
);
if
(
_blocks
.
Count
==
1
)
OnHaveBlocks
();
return
true
;
}
public
bool
Full
=>
_blocks
.
Count
==
Capacity
;
public
Height
BestHeight
=>
_blocks
.
Count
==
0
?
Height
.
Unknown
:
_blocks
.
Keys
.
Max
();
/// <summary>
///
/// </summary>
/// <returns>false if empty</returns>
public
bool
TryGetAndRemoveOldest
(
out
Height
height
,
out
Block
block
)
{
height
=
Height
.
Unknown
;
block
=
default
(
Block
);
if
(
_blocks
.
Count
==
0
)
return
false
;
height
=
_blocks
.
Keys
.
Min
();
block
=
_blocks
[
height
];
_blocks
.
Remove
(
height
);
return
true
;
}
}
}
Breeze/src/Breeze.Wallet/Temp/Util.cs
0 → 100644
View file @
b0838544
using
System
;
using
System.Collections.Generic
;
using
System.Linq
;
using
System.Threading.Tasks
;
namespace
HBitcoin
{
internal
static
class
Util
{
internal
static
byte
[][]
Separate
(
byte
[]
source
,
byte
[]
separator
)
{
var
Parts
=
new
List
<
byte
[
]>
();
var
Index
=
0
;
byte
[]
Part
;
for
(
var
I
=
0
;
I
<
source
.
Length
;
++
I
)
{
if
(
Equals
(
source
,
separator
,
I
))
{
Part
=
new
byte
[
I
-
Index
];
Array
.
Copy
(
source
,
Index
,
Part
,
0
,
Part
.
Length
);
Parts
.
Add
(
Part
);
Index
=
I
+
separator
.
Length
;
I
+=
separator
.
Length
-
1
;
}
}
Part
=
new
byte
[
source
.
Length
-
Index
];
Array
.
Copy
(
source
,
Index
,
Part
,
0
,
Part
.
Length
);
Parts
.
Add
(
Part
);
return
Parts
.
ToArray
();
}
private
static
bool
Equals
(
byte
[]
source
,
byte
[]
separator
,
int
index
)
{
for
(
int
i
=
0
;
i
<
separator
.
Length
;
++
i
)
if
(
index
+
i
>=
source
.
Length
||
source
[
index
+
i
]
!=
separator
[
i
])
return
false
;
return
true
;
}
/// <summary>
/// Splits an array into several smaller arrays.
/// </summary>
/// <typeparam name="T">The type of the array.</typeparam>
/// <param name="array">The array to split.</param>
/// <param name="size">The size of the smaller arrays.</param>
/// <returns>An array containing smaller arrays.</returns>
public
static
IEnumerable
<
IEnumerable
<
T
>>
Split
<
T
>(
T
[]
array
,
int
size
)
{
for
(
var
i
=
0
;
i
<
(
float
)
array
.
Length
/
size
;
i
++)
{
yield
return
array
.
Skip
(
i
*
size
).
Take
(
size
);
}
}
}
}
Breeze/src/Breeze.Wallet/Wallet.cs
View file @
b0838544
using
System
;
using
System
;
using
System.Collections.Generic
;
using
Breeze.Wallet.JsonConverters
;
using
NBitcoin
;
using
NBitcoin
;
using
NBitcoin.JsonConverters
;
using
Newtonsoft.Json
;
namespace
Breeze.Wallet
namespace
Breeze.Wallet
{
{
...
@@ -8,29 +12,210 @@ namespace Breeze.Wallet
...
@@ -8,29 +12,210 @@ namespace Breeze.Wallet
/// </summary>
/// </summary>
public
class
Wallet
public
class
Wallet
{
{
/// <summary>
/// The name of this wallet.
/// </summary>
[
JsonProperty
(
PropertyName
=
"name"
)]
public
string
Name
{
get
;
set
;
}
/// <summary>
/// The seed for this wallet, password encrypted.
/// </summary>
[
JsonProperty
(
PropertyName
=
"encryptedSeed"
)]
public
string
EncryptedSeed
{
get
;
set
;
}
/// <summary>
/// <summary>
/// The chain code.
/// The chain code.
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"chainCode"
)]
[
JsonConverter
(
typeof
(
ByteArrayConverter
))]
public
byte
[]
ChainCode
{
get
;
set
;
}
public
byte
[]
ChainCode
{
get
;
set
;
}
/// <summary>
/// <summary>
/// The network this wallet is for.
/// The network this wallet is for.
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"network"
)]
[
JsonConverter
(
typeof
(
NetworkConverter
))]
public
Network
Network
{
get
;
set
;
}
public
Network
Network
{
get
;
set
;
}
/// <summary>
/// <summary>
/// The time this wallet was created.
/// The time this wallet was created.
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"creationTime"
)]
[
JsonConverter
(
typeof
(
DateTimeOffsetConverter
))]
public
DateTimeOffset
CreationTime
{
get
;
set
;
}
public
DateTimeOffset
CreationTime
{
get
;
set
;
}
/// <summary>
/// <summary>
/// The location of the wallet file on the local system.
/// The location of the wallet file on the local system.
/// </summary>
/// </summary>
[
JsonProperty
(
PropertyName
=
"walletFilePath"
)]
public
string
WalletFilePath
{
get
;
set
;
}
public
string
WalletFilePath
{
get
;
set
;
}
/// <summary>
/// <summary>
/// The key used to generate keys.
/// The root of the accounts tree.
/// </summary>
[
JsonProperty
(
PropertyName
=
"accountsRoot"
)]
public
IEnumerable
<
AccountRoot
>
AccountsRoot
{
get
;
set
;
}
}
/// <summary>
/// The root for the accounts for any type of coins.
/// </summary>
public
class
AccountRoot
{
/// <summary>
/// The type of coin, Bitcoin or Stratis.
/// </summary>
[
JsonProperty
(
PropertyName
=
"coinType"
)]
public
CoinType
CoinType
{
get
;
set
;
}
/// <summary>
/// The accounts used in the wallet.
/// </summary>
[
JsonProperty
(
PropertyName
=
"accounts"
)]
public
IEnumerable
<
HdAccount
>
Accounts
{
get
;
set
;
}
}
/// <summary>
/// The type of coin, as specified in BIP44.
/// </summary>
public
enum
CoinType
{
Bitcoin
=
0
,
Stratis
=
105
}
/// <summary>
/// An Hd account's details.
/// </summary>
public
class
HdAccount
{
/// <summary>
/// The index of the account.
/// </summary>
/// <remarks>
/// According to BIP44, an account at index (i) can only be created when the account
/// at index (i - 1) contains transactions.
/// </remarks>
[
JsonProperty
(
PropertyName
=
"index"
)]
public
int
Index
{
get
;
set
;
}
/// <summary>
/// The name of this account.
/// </summary>
[
JsonProperty
(
PropertyName
=
"name"
)]
public
string
Name
{
get
;
set
;
}
/// <summary>
/// An extended pub key used to generate addresses.
/// </summary>
[
JsonProperty
(
PropertyName
=
"extPubKey"
)]
public
string
ExtendedPubKey
{
get
;
set
;
}
/// <summary>
/// Gets or sets the creation time.
/// </summary>
[
JsonProperty
(
PropertyName
=
"creationTime"
)]
[
JsonConverter
(
typeof
(
DateTimeOffsetConverter
))]
public
DateTimeOffset
CreationTime
{
get
;
set
;
}
/// <summary>
/// The list of external addresses, typically used for receiving money.
/// </summary>
[
JsonProperty
(
PropertyName
=
"externalAddresses"
)]
public
IEnumerable
<
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
;
}
}
/// <summary>
/// An Hd address.
/// </summary>
public
class
HdAddress
{
/// <summary>
/// The index of the address.
/// </summary>
[
JsonProperty
(
PropertyName
=
"index"
)]
public
int
Index
{
get
;
set
;
}
/// <summary>
/// Gets or sets the creation time.
/// </summary>
/// </summary>
public
ExtKey
ExtendedKey
{
get
;
set
;
}
[
JsonProperty
(
PropertyName
=
"creationTime"
)]
[
JsonConverter
(
typeof
(
DateTimeOffsetConverter
))]
public
DateTimeOffset
CreationTime
{
get
;
set
;
}
/// <summary>
/// The script pub key for this address.
/// </summary>
[
JsonProperty
(
PropertyName
=
"scriptPubKey"
)]
[
JsonConverter
(
typeof
(
ScriptJsonConverter
))]
public
Script
ScriptPubKey
{
get
;
set
;
}
/// <summary>
/// The Base58 representation of this address.
/// </summary>
[
JsonProperty
(
PropertyName
=
"address"
)]
public
string
Address
{
get
;
set
;
}
/// <summary>
/// A path to the address as defined in BIP44.
/// </summary>
[
JsonProperty
(
PropertyName
=
"hdPath"
)]
public
string
HdPath
{
get
;
set
;
}
/// <summary>
/// A list detailing which blocks have been scanned for this address.
/// </summary>
[
JsonIgnore
]
public
SortedList
<
int
,
int
>
BlocksScanned
{
get
;
set
;
}
/// <summary>
/// A list of transactions involving this address.
/// </summary>
[
JsonProperty
(
PropertyName
=
"transactions"
)]
public
IEnumerable
<
TransactionData
>
Transactions
{
get
;
set
;
}
}
/// <summary>
/// An object containing transaction data.
/// </summary>
public
class
TransactionData
{
/// <summary>
/// Transaction id.
/// </summary>
[
JsonProperty
(
PropertyName
=
"id"
)]
public
string
Id
{
get
;
set
;
}
/// <summary>
/// The transaction amount.
/// </summary>
[
JsonProperty
(
PropertyName
=
"amount"
)]
public
Money
Amount
{
get
;
set
;
}
/// <summary>
/// The height of the block including this transaction.
/// </summary>
[
JsonProperty
(
PropertyName
=
"blockHeight"
)]
public
int
BlockHeight
{
get
;
set
;
}
/// <summary>
/// Whether this transaction has been confirmed or not.
/// </summary>
[
JsonProperty
(
PropertyName
=
"confirmed"
)]
public
bool
Confirmed
{
get
;
set
;
}
/// <summary>
/// Gets or sets the creation time.
/// </summary>
[
JsonProperty
(
PropertyName
=
"creationTime"
)]
[
JsonConverter
(
typeof
(
DateTimeOffsetConverter
))]
public
DateTimeOffset
CreationTime
{
get
;
set
;
}
}
}
}
}
\ No newline at end of file
Breeze/src/Breeze.Wallet/WalletFeature.cs
View file @
b0838544
...
@@ -41,8 +41,7 @@ namespace Breeze.Wallet
...
@@ -41,8 +41,7 @@ namespace Breeze.Wallet
.
AddFeature
<
WalletFeature
>()
.
AddFeature
<
WalletFeature
>()
.
FeatureServices
(
services
=>
.
FeatureServices
(
services
=>
{
{
services
.
AddTransient
<
IWalletWrapper
,
WalletWrapper
>();
services
.
AddSingleton
<
ITrackerWrapper
,
TrackerWrapper
>();
services
.
AddTransient
<
ITrackerWrapper
,
TrackerWrapper
>();
services
.
AddSingleton
<
IWalletManager
,
WalletManager
>();
services
.
AddSingleton
<
IWalletManager
,
WalletManager
>();
services
.
AddSingleton
<
WalletController
>();
services
.
AddSingleton
<
WalletController
>();
});
});
...
...
Breeze/src/Breeze.Wallet/WalletFile.cs
deleted
100644 → 0
View file @
f24e1da0
using
System
;
using
NBitcoin
;
using
Newtonsoft.Json
;
using
Breeze.Wallet.JsonConverters
;
namespace
Breeze.Wallet
{
/// <summary>
/// An object representing a wallet on a local file system.
/// </summary>
public
class
WalletFile
{
/// <summary>
/// The seed for this wallet, password encrypted.
/// </summary>
[
JsonProperty
(
PropertyName
=
"encryptedSeed"
)]
public
string
EncryptedSeed
{
get
;
set
;
}
/// <summary>
/// The chain code.
/// </summary>
[
JsonProperty
(
PropertyName
=
"chainCode"
)]
[
JsonConverter
(
typeof
(
ByteArrayConverter
))]
public
byte
[]
ChainCode
{
get
;
set
;
}
/// <summary>
/// The network this wallet is for.
/// </summary>
[
JsonProperty
(
PropertyName
=
"network"
)]
[
JsonConverter
(
typeof
(
NetworkConverter
))]
public
Network
Network
{
get
;
set
;
}
/// <summary>
/// The time this wallet was created.
/// </summary>
[
JsonProperty
(
PropertyName
=
"creationTime"
)]
[
JsonConverter
(
typeof
(
DateTimeOffsetConverter
))]
public
DateTimeOffset
CreationTime
{
get
;
set
;
}
}
}
Breeze/src/Breeze.Wallet/WalletManager.cs
View file @
b0838544
using
System
;
using
System
;
using
System.Collections.Generic
;
using
System.IO
;
using
System.IO
;
using
System.Linq
;
using
Breeze.Wallet.Helpers
;
using
Breeze.Wallet.Models
;
using
NBitcoin
;
using
NBitcoin
;
using
Newtonsoft.Json
;
using
Newtonsoft.Json
;
using
Stratis.Bitcoin.Utilities
;
namespace
Breeze.Wallet
namespace
Breeze.Wallet
{
{
...
@@ -10,64 +15,199 @@ namespace Breeze.Wallet
...
@@ -10,64 +15,199 @@ namespace Breeze.Wallet
/// </summary>
/// </summary>
public
class
WalletManager
:
IWalletManager
public
class
WalletManager
:
IWalletManager
{
{
public
List
<
Wallet
>
Wallets
{
get
;
}
public
WalletManager
()
{
this
.
Wallets
=
new
List
<
Wallet
>();
}
/// <inheritdoc />
/// <inheritdoc />
public
Mnemonic
CreateWallet
(
string
password
,
string
walletFilePath
,
Network
network
,
string
passphrase
=
null
)
public
Mnemonic
CreateWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
passphrase
=
null
)
{
// for now the passphrase is set to be the password by default.
if
(
passphrase
==
null
)
{
{
passphrase
=
password
;
}
// generate the root seed used to generate keys from a mnemonic picked at random
// generate the root seed used to generate keys from a mnemonic picked at random
// and a passphrase optionally provided by the user
// and a passphrase optionally provided by the user
Mnemonic
mnemonic
=
new
Mnemonic
(
Wordlist
.
English
,
WordCount
.
Twelve
);
Mnemonic
mnemonic
=
new
Mnemonic
(
Wordlist
.
English
,
WordCount
.
Twelve
);
ExtKey
extendedKey
=
mnemonic
.
DeriveExtKey
(
passphrase
);
ExtKey
extendedKey
=
mnemonic
.
DeriveExtKey
(
passphrase
);
// create a wallet file
// create a wallet file
this
.
GenerateWalletFile
(
password
,
walletFilePath
,
network
,
extendedKey
);
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
WalletHelpers
.
GetNetwork
(
network
)
,
extendedKey
);
this
.
Load
(
wallet
);
return
mnemonic
;
return
mnemonic
;
}
}
/// <inheritdoc />
/// <inheritdoc />
public
Wallet
LoadWallet
(
string
password
,
string
walletFilePath
)
public
Wallet
LoadWallet
(
string
password
,
string
folderPath
,
string
name
)
{
{
string
walletFilePath
=
Path
.
Combine
(
folderPath
,
$"
{
name
}
.json"
);
if
(!
File
.
Exists
(
walletFilePath
))
if
(!
File
.
Exists
(
walletFilePath
))
throw
new
FileNotFoundException
(
$"No wallet file found at
{
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
File
walletFile
=
JsonConvert
.
DeserializeObject
<
WalletFile
>(
File
.
ReadAllText
(
walletFilePath
));
Wallet
wallet
=
JsonConvert
.
DeserializeObject
<
Wallet
>(
File
.
ReadAllText
(
walletFilePath
));
// decrypt the private key and use it to regenerate the seed
this
.
Load
(
wallet
);
var
privateKey
=
Key
.
Parse
(
walletFile
.
EncryptedSeed
,
password
,
walletFile
.
Network
)
;
return
wallet
;
var
seedExtKey
=
new
ExtKey
(
privateKey
,
walletFile
.
ChainCode
);
}
Wallet
wallet
=
new
Wallet
/// <inheritdoc />
public
Wallet
RecoverWallet
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
mnemonic
,
string
passphrase
=
null
,
DateTimeOffset
?
creationTime
=
null
)
{
{
ChainCode
=
walletFile
.
ChainCode
,
// for now the passphrase is set to be the password by default.
CreationTime
=
walletFile
.
CreationTime
,
if
(
passphrase
==
null
)
Network
=
walletFile
.
Network
,
{
WalletFilePath
=
walletFilePath
,
passphrase
=
password
;
ExtendedKey
=
seedExtKey
}
};
// generate the root seed used to generate keys
ExtKey
extendedKey
=
(
new
Mnemonic
(
mnemonic
)).
DeriveExtKey
(
passphrase
);
// create a wallet file
Wallet
wallet
=
this
.
GenerateWalletFile
(
password
,
folderPath
,
name
,
WalletHelpers
.
GetNetwork
(
network
),
extendedKey
,
creationTime
);
this
.
Load
(
wallet
);
return
wallet
;
return
wallet
;
}
}
/// <inheritdoc />
/// <inheritdoc />
public
Wallet
RecoverWallet
(
Mnemonic
mnemonic
,
string
password
,
string
walletFilePath
,
Network
network
,
string
passphrase
=
null
,
DateTimeOffset
?
creationTime
=
null
)
public
string
CreateNewAccount
(
string
walletName
,
CoinType
coinType
,
string
accountName
,
string
password
)
{
{
// generate the root seed used to generate keys
Wallet
wallet
=
this
.
Wallets
.
SingleOrDefault
(
w
=>
w
.
Name
==
walletName
);
ExtKey
extendedKey
=
mnemonic
.
DeriveExtKey
(
passphrase
);
if
(
wallet
==
null
)
{
throw
new
Exception
(
$"No wallet with name
{
walletName
}
could be found."
);
}
// create a wallet file
// get the accounts for this type of coin
WalletFile
walletFile
=
this
.
GenerateWalletFile
(
password
,
walletFilePath
,
network
,
extendedKey
,
creationTime
);
var
accounts
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
.
ToList
();
int
newAccountIndex
=
0
;
Wallet
wallet
=
new
Wallet
// validate account creation
if
(
accounts
.
Any
())
{
{
ChainCode
=
walletFile
.
ChainCode
,
// check account with same name doesn't already exists
CreationTime
=
walletFile
.
CreationTime
,
if
(
accounts
.
Any
(
a
=>
a
.
Name
==
accountName
))
Network
=
walletFile
.
Network
,
{
WalletFilePath
=
walletFilePath
,
throw
new
Exception
(
$"Account with name '
{
accountName
}
' already exists in '
{
walletName
}
'."
);
ExtendedKey
=
extendedKey
}
};
return
wallet
;
// check account at index i - 1 contains transactions.
int
lastAccountIndex
=
accounts
.
Max
(
a
=>
a
.
Index
);
HdAccount
previousAccount
=
accounts
.
Single
(
a
=>
a
.
Index
==
lastAccountIndex
);
if
(!
previousAccount
.
ExternalAddresses
.
Any
(
addresses
=>
addresses
.
Transactions
.
Any
())
&&
!
previousAccount
.
InternalAddresses
.
Any
(
addresses
=>
addresses
.
Transactions
.
Any
()))
{
throw
new
Exception
(
$"Cannot create new account '
{
accountName
}
' in '
{
walletName
}
' if the previous account '
{
previousAccount
.
Name
}
' has not been used."
);
}
newAccountIndex
=
lastAccountIndex
+
1
;
}
// get the extended pub key used to generate addresses for this account
var
privateKey
=
Key
.
Parse
(
wallet
.
EncryptedSeed
,
password
,
wallet
.
Network
);
var
seedExtKey
=
new
ExtKey
(
privateKey
,
wallet
.
ChainCode
);
KeyPath
keyPath
=
new
KeyPath
(
$"m/44'/
{(
int
)
coinType
}
'/
{
newAccountIndex
}
'"
);
ExtKey
accountExtKey
=
seedExtKey
.
Derive
(
keyPath
);
ExtPubKey
accountExtPubKey
=
accountExtKey
.
Neuter
();
accounts
.
Add
(
new
HdAccount
{
Index
=
newAccountIndex
,
ExtendedPubKey
=
accountExtPubKey
.
ToString
(
wallet
.
Network
),
ExternalAddresses
=
new
List
<
HdAddress
>(),
InternalAddresses
=
new
List
<
HdAddress
>(),
Name
=
accountName
,
CreationTime
=
DateTimeOffset
.
Now
});
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
=
accounts
;
this
.
SaveToFile
(
wallet
);
return
accountName
;
}
/// <inheritdoc />
public
string
CreateNewAddress
(
string
walletName
,
CoinType
coinType
,
string
accountName
)
{
Wallet
wallet
=
this
.
Wallets
.
SingleOrDefault
(
w
=>
w
.
Name
==
walletName
);
if
(
wallet
==
null
)
{
throw
new
Exception
(
$"No wallet with name
{
walletName
}
could be found."
);
}
// get the account
HdAccount
account
=
wallet
.
AccountsRoot
.
Single
(
a
=>
a
.
CoinType
==
coinType
).
Accounts
.
SingleOrDefault
(
a
=>
a
.
Name
==
accountName
);
if
(
account
==
null
)
{
throw
new
Exception
(
$"No account with name
{
accountName
}
could be found."
);
}
int
newAddressIndex
=
0
;
// validate address creation
if
(
account
.
ExternalAddresses
.
Any
())
{
// check last created address contains transactions.
int
lastAddressIndex
=
account
.
ExternalAddresses
.
Max
(
a
=>
a
.
Index
);
var
lastAddress
=
account
.
ExternalAddresses
.
SingleOrDefault
(
a
=>
a
.
Index
==
lastAddressIndex
);
if
(
lastAddress
!=
null
&&
!
lastAddress
.
Transactions
.
Any
())
{
throw
new
Exception
(
$"Cannot create new address in account '
{
accountName
}
' if the previous address '
{
lastAddress
.
Address
}
' has not been used."
);
}
newAddressIndex
=
lastAddressIndex
+
1
;
}
// generate new receiving address
BitcoinPubKeyAddress
address
=
this
.
GenerateAddress
(
account
.
ExtendedPubKey
,
newAddressIndex
,
false
,
wallet
.
Network
);
// add address details
account
.
ExternalAddresses
=
account
.
ExternalAddresses
.
Concat
(
new
[]
{
new
HdAddress
{
Index
=
newAddressIndex
,
HdPath
=
CreateBip44Path
(
coinType
,
account
.
Index
,
newAddressIndex
,
false
),
ScriptPubKey
=
address
.
ScriptPubKey
,
Address
=
address
.
ToString
(),
Transactions
=
new
List
<
TransactionData
>(),
CreationTime
=
DateTimeOffset
.
Now
}});
this
.
SaveToFile
(
wallet
);
return
address
.
ToString
();
}
public
WalletGeneralInfoModel
GetGeneralInfo
(
string
name
)
{
throw
new
System
.
NotImplementedException
();
}
public
WalletBalanceModel
GetBalance
(
string
walletName
)
{
throw
new
System
.
NotImplementedException
();
}
public
WalletHistoryModel
GetHistory
(
string
walletName
)
{
throw
new
System
.
NotImplementedException
();
}
public
WalletBuildTransactionModel
BuildTransaction
(
string
password
,
string
address
,
Money
amount
,
string
feeType
,
bool
allowUnconfirmed
)
{
throw
new
System
.
NotImplementedException
();
}
public
bool
SendTransaction
(
string
transactionHex
)
{
throw
new
System
.
NotImplementedException
();
}
}
/// <inheritdoc />
/// <inheritdoc />
...
@@ -86,23 +226,32 @@ namespace Breeze.Wallet
...
@@ -86,23 +226,32 @@ namespace Breeze.Wallet
/// Generates the wallet file.
/// Generates the wallet file.
/// </summary>
/// </summary>
/// <param name="password">The password used to encrypt sensitive info.</param>
/// <param name="password">The password used to encrypt sensitive info.</param>
/// <param name="walletFilePath">The location of the wallet file.</param>
/// <param name="folderPath">The folder where the wallet will be generated.</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>
private
Wallet
File
GenerateWalletFile
(
string
password
,
string
walletFilePath
,
Network
network
,
ExtKey
extendedKey
,
DateTimeOffset
?
creationTime
=
null
)
private
Wallet
GenerateWalletFile
(
string
password
,
string
folderPath
,
string
name
,
Network
network
,
ExtKey
extendedKey
,
DateTimeOffset
?
creationTime
=
null
)
{
{
string
walletFilePath
=
Path
.
Combine
(
folderPath
,
$"
{
name
}
.json"
);
if
(
File
.
Exists
(
walletFilePath
))
if
(
File
.
Exists
(
walletFilePath
))
throw
new
InvalidOperationException
(
$"Wallet already exists at
{
walletFilePath
}
"
);
throw
new
InvalidOperationException
(
$"Wallet already exists at
{
walletFilePath
}
"
);
Wallet
File
walletFile
=
new
WalletFile
Wallet
walletFile
=
new
Wallet
{
{
Name
=
name
,
EncryptedSeed
=
extendedKey
.
PrivateKey
.
GetEncryptedBitcoinSecret
(
password
,
network
).
ToWif
(),
EncryptedSeed
=
extendedKey
.
PrivateKey
.
GetEncryptedBitcoinSecret
(
password
,
network
).
ToWif
(),
ChainCode
=
extendedKey
.
ChainCode
,
ChainCode
=
extendedKey
.
ChainCode
,
CreationTime
=
creationTime
??
DateTimeOffset
.
Now
,
CreationTime
=
creationTime
??
DateTimeOffset
.
Now
,
Network
=
network
Network
=
network
,
AccountsRoot
=
new
List
<
AccountRoot
>
{
new
AccountRoot
{
Accounts
=
new
List
<
HdAccount
>(),
CoinType
=
CoinType
.
Bitcoin
},
new
AccountRoot
{
Accounts
=
new
List
<
HdAccount
>(),
CoinType
=
CoinType
.
Stratis
}
},
WalletFilePath
=
walletFilePath
};
};
// create a folder if none exists and persist the file
// create a folder if none exists and persist the file
...
@@ -111,5 +260,51 @@ namespace Breeze.Wallet
...
@@ -111,5 +260,51 @@ namespace Breeze.Wallet
return
walletFile
;
return
walletFile
;
}
}
/// <summary>
/// Saves the wallet into the file system.
/// </summary>
/// <param name="wallet">The wallet to save.</param>
private
void
SaveToFile
(
Wallet
wallet
)
{
File
.
WriteAllText
(
wallet
.
WalletFilePath
,
JsonConvert
.
SerializeObject
(
wallet
,
Formatting
.
Indented
));
}
/// <summary>
/// Loads the wallet to be used by the manager.
/// </summary>
/// <param name="wallet">The wallet to load.</param>
private
void
Load
(
Wallet
wallet
)
{
if
(
this
.
Wallets
.
All
(
w
=>
w
.
Name
!=
wallet
.
Name
))
{
this
.
Wallets
.
Add
(
wallet
);
}
}
private
BitcoinPubKeyAddress
GenerateAddress
(
string
accountExtPubKey
,
int
index
,
bool
isChange
,
Network
network
)
{
int
change
=
isChange
?
1
:
0
;
KeyPath
keyPath
=
new
KeyPath
(
$"
{
change
}
/
{
index
}
"
);
ExtPubKey
extPubKey
=
ExtPubKey
.
Parse
(
accountExtPubKey
).
Derive
(
keyPath
);
return
extPubKey
.
PubKey
.
GetAddress
(
network
);
}
/// <summary>
/// Creates the bip44 path.
/// </summary>
/// <param name="coinType">Type of the coin.</param>
/// <param name="accountIndex">Index of the account.</param>
/// <param name="addressIndex">Index of the address.</param>
/// <param name="isChange">if set to <c>true</c> [is change].</param>
/// <returns></returns>
public
static
string
CreateBip44Path
(
CoinType
coinType
,
int
accountIndex
,
int
addressIndex
,
bool
isChange
=
false
)
{
//// populate the items according to the BIP44 path
//// [m/purpose'/coin_type'/account'/change/address_index]
int
change
=
isChange
?
1
:
0
;
return
$"m/44'/
{(
int
)
coinType
}
'/
{
accountIndex
}
'/
{
change
}
/
{
addressIndex
}
"
;
}
}
}
}
}
Breeze/src/Breeze.Wallet/Wrappers/IWalletWrapper.cs
deleted
100644 → 0
View file @
f24e1da0
using
Breeze.Wallet.Models
;
using
NBitcoin
;
namespace
Breeze.Wallet.Wrappers
{
/// <summary>
/// An interface enabling wallet operations.
/// </summary>
public
interface
IWalletWrapper
{
string
Create
(
string
password
,
string
folderPath
,
string
name
,
string
network
);
WalletModel
Load
(
string
password
,
string
folderPath
,
string
name
);
WalletModel
Recover
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
mnemonic
);
WalletGeneralInfoModel
GetGeneralInfo
(
string
walletName
);
WalletBalanceModel
GetBalance
(
string
walletName
);
WalletHistoryModel
GetHistory
(
string
walletName
);
WalletBuildTransactionModel
BuildTransaction
(
string
password
,
string
address
,
Money
amount
,
string
feeType
,
bool
allowUnconfirmed
);
bool
SendTransaction
(
string
transactionHex
);
}
}
Breeze/src/Breeze.Wallet/Wrappers/WalletWrapper.cs
deleted
100644 → 0
View file @
f24e1da0
using
System
;
using
System.IO
;
using
System.Linq
;
using
Breeze.Wallet.Helpers
;
using
Breeze.Wallet.Models
;
using
NBitcoin
;
namespace
Breeze.Wallet.Wrappers
{
/// <summary>
/// An implementation of the <see cref="IWalletWrapper"/> interface.
/// </summary>
public
class
WalletWrapper
:
IWalletWrapper
{
private
readonly
IWalletManager
walletManager
;
public
WalletWrapper
(
IWalletManager
walletManager
)
{
this
.
walletManager
=
walletManager
;
}
/// <summary>
/// Creates a wallet on the local device.
/// </summary>
/// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the wallet will be saved.</param>
/// <param name="name">The name of the wallet.</param>
/// <param name="network">The network for which to create a wallet.</param>
/// <returns>A mnemonic allowing recovery of the wallet.</returns>
public
string
Create
(
string
password
,
string
folderPath
,
string
name
,
string
network
)
{
Mnemonic
mnemonic
=
this
.
walletManager
.
CreateWallet
(
password
,
Path
.
Combine
(
folderPath
,
$"
{
name
}
.json"
),
WalletHelpers
.
GetNetwork
(
network
),
password
);
return
mnemonic
.
ToString
();
}
/// <summary>
/// Loads a wallet from the local device.
/// </summary>
/// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the wallet will be loaded.</param>
/// <param name="name">The name of the wallet.</param>
/// <returns>The wallet loaded from the local device</returns>
public
WalletModel
Load
(
string
password
,
string
folderPath
,
string
name
)
{
Wallet
wallet
=
this
.
walletManager
.
LoadWallet
(
password
,
Path
.
Combine
(
folderPath
,
$"
{
name
}
.json"
));
//TODO review here which data should be returned
return
new
WalletModel
{
Network
=
wallet
.
Network
.
Name
,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName
=
wallet
.
WalletFilePath
};
}
/// <summary>
/// Recovers a wallet from the local device.
/// </summary>
/// <param name="password">The user's password.</param>
/// <param name="folderPath">The folder where the wallet will be loaded.</param>
/// <param name="name">The name of the wallet.</param>
/// <param name="network">The network in which to creae this wallet</param>
/// <param name="mnemonic">The user's mnemonic for the wallet.</param>
/// <returns></returns>
public
WalletModel
Recover
(
string
password
,
string
folderPath
,
string
name
,
string
network
,
string
mnemonic
)
{
Wallet
wallet
=
this
.
walletManager
.
RecoverWallet
(
new
Mnemonic
(
mnemonic
),
password
,
Path
.
Combine
(
folderPath
,
$"
{
name
}
.json"
),
WalletHelpers
.
GetNetwork
(
network
),
password
);
//TODO review here which data should be returned
return
new
WalletModel
{
Network
=
wallet
.
Network
.
Name
,
// Addresses = wallet.GetFirstNAddresses(10).Select(a => a.ToWif()),
FileName
=
wallet
.
WalletFilePath
};
}
public
WalletGeneralInfoModel
GetGeneralInfo
(
string
name
)
{
throw
new
System
.
NotImplementedException
();
}
public
WalletBalanceModel
GetBalance
(
string
walletName
)
{
throw
new
System
.
NotImplementedException
();
}
public
WalletHistoryModel
GetHistory
(
string
walletName
)
{
throw
new
System
.
NotImplementedException
();
}
public
WalletBuildTransactionModel
BuildTransaction
(
string
password
,
string
address
,
Money
amount
,
string
feeType
,
bool
allowUnconfirmed
)
{
throw
new
System
.
NotImplementedException
();
}
public
bool
SendTransaction
(
string
transactionHex
)
{
throw
new
System
.
NotImplementedException
();
}
}
}
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