1
0
forked from cheng/wallet

Merge remote-tracking branch 'origin/master' into docs

This commit is contained in:
reaction.la 2022-05-10 14:50:15 +10:00
commit ba1908f287
No known key found for this signature in database
GPG Key ID: 99914792148C8388
14 changed files with 31274 additions and 15920 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
sqlite3/sqlite-doc/
*.bat
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
@ -268,4 +269,4 @@ paket-files/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
*.pyc

View File

@ -14,7 +14,7 @@
#include <memory> // for shared_ptr, unique_ptr
#include <span>
#include "ISqlite3.h"
#include "sqlite3.h"
#include "sqlite3/sqlite3.h"
static auto error_message(int rc, sqlite3* pdb) {
return std::string("Sqlite3 Error: ") + sqlite3_errmsg(pdb) + ". Sqlite3 error number=" + std::to_string(rc);

View File

@ -3,7 +3,8 @@ using ro::base58;
display_wallet::display_wallet(wxWindow* parent, wxFileName& walletfile) :
wxPanel(parent, myID_WALLET_UI, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("Wallet")),
m_db(nullptr),
m_menuitem_close(this, &display_wallet::close_menu_event_handler)
m_menuitem_close(this, &display_wallet::close_menu_event_handler),
m_menuitem_add_name(this, &display_wallet::add_name_event_handler)
{
wxLogMessage(_T("Loading %s"), walletfile.GetFullPath());
if (!walletfile.IsOk() || !walletfile.HasName() || !walletfile.HasExt()) throw MyException("unexpected file name");
@ -61,17 +62,31 @@ display_wallet::display_wallet(wxWindow* parent, wxFileName& walletfile) :
singletonFrame->m_LastUsedSqlite.Assign(walletfile);
wxMenu* menuFile{ singletonFrame->GetMenuBar()->GetMenu(0) };
m_menuitem_close.Insert(menuFile, 1, "close", "test");
m_menuitem_close.Insert(menuFile, 1, "close", "close wallet");
singletonFrame->GetMenuBar()->EnableTop(1, true); //enable edit menu.
wxMenu* menuEdit{ singletonFrame->GetMenuBar()->GetMenu(1) };
m_menuitem_add_name.Insert(menuEdit, 0, "add name", "create new Zooko identity");
}
display_wallet::~display_wallet() {
assert(true);
singletonFrame->GetMenuBar()->EnableTop(1, false); //disable edit menu.
}
void display_wallet::close_menu_event_handler(wxCommandEvent& event) {
wxMessageDialog dlg(this, event.GetString(), wsz_error, wxICON_ERROR);
Close(true);
}
void display_wallet::add_name_event_handler(wxCommandEvent& event) {
wxMessageDialog dlg(this, "not yet implemented", wsz_error, wxICON_ERROR);
dlg.SetId(myID_ERRORMESSAGE);
dlg.ShowModal();
}
void display_wallet::OnClose(wxCloseEvent& event) {
// This event gives you the opportunity to clean up anything that needs explicit cleanup, albeit if you have done your work right nothing should need explicit cleanup,
// and to object to the closing in a "file not saved" type situation.

View File

@ -11,7 +11,8 @@ private:
wxBoxSizer* m_lSizer;
wxBoxSizer* m_rSizer;
void close_menu_event_handler(wxCommandEvent&);
// MenuLink m_close(display_wallet::close);
void add_name_event_handler(wxCommandEvent&);
MenuLink m_menuitem_close;
MenuLink m_menuitem_add_name;
void OnClose(wxCloseEvent& event);
};

118
frame.cpp
View File

@ -144,11 +144,11 @@ try {
menuHelp->Bind(wxEVT_MENU, &Frame::OnAbout, this, wxID_ABOUT);
wxMenuBar* menuBar = new wxMenuBar;
menuBar->Append(menuFile, menu_strings[0].head);
menuBar->Append(new wxMenu, menu_strings[1].head);
menuBar->Append(new wxMenu, menu_strings[1].head); //Edit menu, initially empty and disabled
menuBar->Append(menuHelp, menu_strings[2].head);
SetMenuBar(menuBar);
CreateStatusBar();
menuBar->EnableTop(1, false);
menuBar->EnableTop(1, false); //disable edit menu.
// child controls
m_LastUsedSqlite.Assign(singletonApp->pConfig->Read(_T("/Wallet/LastUsed"), _T("")));
if (!m_LastUsedSqlite.IsOk() || !m_LastUsedSqlite.HasName() || !m_LastUsedSqlite.HasExt()) {
@ -223,56 +223,17 @@ void Frame::OnDeleteConfiguration(wxCommandEvent&)
}
using ro::bin2hex, ro::to_base64_string;
void Frame::NewWalletNewSecret(wxCommandEvent&) {
wxMessageBox(_T("new wallet new secret event"), _T(""));
/* If LastUsed is the empty string, check if default filename exists. If it exists, set Last Used to default file name
If LastUsed is not the empty string, or no longer the empty string, attempt to open indicated file. If open fails, or reading the opened file produces bad results, abort with exception
If LastUsed is still the empty string, attempt to create default filename. If creation fails, abort with exception. If it succeeds, set LastUsed to default filename.
The exception in unit test should simply generate an error message, but if run during initialization, should bring up the more complex UI for constructing or selecting your wallet file.*/
ILogMessage("\tWallet file");
/*
bool fWalletNameOk{ false };
wxStandardPaths& StandardPaths(wxStandardPaths::Get());
StandardPaths.UseAppInfo(3);
std::unique_ptr<ISqlite3> db;
if (fWalletNameOk) {
if (!m_LastUsedSqlite.FileExists())
throw MyException((std::string("Expected wallet:\n") + LastUsedSqlite.GetFullPath() + "\nfile not found").c_str());
db.reset(Sqlite3_open(m_LastUsedSqlite.GetFullPath().ToUTF8()));
sql_read_from_misc read_from_misc(db);
if (!read_from_misc(1) || read_from_misc.value<int64_t>() != WALLET_FILE_IDENTIFIER)
throw MyException(sz_unrecognizable_wallet_file_format);
if (!read_from_misc(2) || read_from_misc.value<int64_t>() != WALLET_FILE_SCHEMA_VERSION_0_0)
throw MyException(sz_unrecognized_wallet_schema);
if (!read_from_misc(4))
throw MyException(sz_mastersecret_missing);
ristretto255::CMasterSecret MasterSecret(*read_from_misc.value<ristretto255::scalar>());
ro::sql read_keys(db, R"|(SELECT * FROM "Keys" LIMIT 5;)|");
sql_read_name read_name(db);
// db.reset(nullptr);// Force error of premature destruction of Isqlite3
while (read_keys.step() == Icompiled_sql::ROW) {
auto pubkey = read_keys.column<ristretto255::point>(0);
auto id = read_keys.column<int>(1);
auto use = read_keys.column<int>(2);
if (use != 1)throw MyException(sz_unknown_secret_key_algorithm);
if (!read_name(id)) throw MyException(sz_no_corresponding_entry);
const char* name = read_name.name();
if (MasterSecret(name).timesBase() != *pubkey)throw MyException(sz_name_does_not_correspond);
wxLogMessage(_T("\t\t\"%s\" has expected public key 0x%s"), name, (wxString)bin2hex(*pubkey));
}
}
else {
// At this point in the code the filename LastUsedSqlite is a bad filename, normally the empty string, and the default wallet file does not exist in the default location.
// Construct default wallet and filename*//*
wxFileName path{ StandardPaths.GetUserLocalDataDir() };
try {
// Disk operations to create wallet, which may throw.
// This try/catch block exists to catch disk io issues.
if (!path.DirExists())path.Mkdir();
if (!DefaultSqlite.DirExists())DefaultSqlite.Mkdir();
db.reset(Sqlite3_create(DefaultSqlite.GetFullPath().ToUTF8()));
db->exec(R"|(
void Frame::NewWallet(wxFileName& filename, ristretto255::hash<256>& secret) {
/*If creation fails, abort with exception. If it succeeds, set LastUsed to default filename.
The exception in unit test should simply generate an error message, but if run during initialization,
should bring up the more complex UI for constructing or selecting your wallet file.*/
wxLogMessage(_wx("New wallet file %s"), filename.GetFullPath());
std::unique_ptr<ISqlite3> db{ nullptr };
try {
// Disk operations to create wallet, which may throw.
// This try/catch block exists to catch disk io issues.
db.reset(Sqlite3_create(filename.GetFullPath().ToUTF8()));
db->exec(R"|(
PRAGMA journal_mode = WAL;
PRAGMA synchronous = 1;
BEGIN TRANSACTION;
@ -290,34 +251,29 @@ CREATE TABLE "Misc"(
"m" BLOB
);
COMMIT;)|");
LastUsedSqlite = DefaultSqlite;
singletonApp->pConfig->Write(_T("LastUsed"), DefaultSqlite.GetFullPath());
wxLogMessage("\t\tConstructing default wallet %s", DefaultSqlite.GetFullPath());
// We now have a working wallet file with no valid data. Attempting to create a strong random secret, a name, and public and private keys for that name.
wxLogMessage("\t\tGenerating random 128 bit wallet secret");
auto text_secret{ DeriveTextSecret(ristretto255::scalar::random(), 1) };
ro::msec start_time{ ro::msec_since_epoch() };
ristretto255::CMasterSecret MasterSecret(DeriveStrongSecret(&text_secret[0]));
decltype(start_time) end_time{ ro::msec_since_epoch() };
wxLogMessage("\t\tStrong secret derivation took %d milliseconds", (end_time - start_time).count());
sql_update_to_misc update_to_misc(db);
update_to_misc(1, WALLET_FILE_IDENTIFIER);
update_to_misc(2, WALLET_FILE_SCHEMA_VERSION_0_0);
update_to_misc(3, &text_secret[0]);
update_to_misc(4, MasterSecret);
sql_insert_name insert_name(db);
const char* const cpsz("Unit Tester");
insert_name(cpsz, MasterSecret(cpsz).timesBase());
}
catch (const MyException& e) {
ILogError(R"|(Failed to create or failed to properly initialize wallet)|");
errorCode = 20;
szError = e.what();
ILogError(szError.c_str());
}
} // End of wallet creation branch*/
wxLogMessage("\t\tConstructing default wallet %s", filename.GetFullPath());
// We now have a working wallet file with no valid data. Attempting to create a strong random secret, a name, and public and private keys for that name.
wxLogMessage("\t\tGenerating random 128 bit wallet secret");
auto text_secret{ DeriveTextSecret(ristretto255::scalar::random(), 1) };
ro::msec start_time{ ro::msec_since_epoch() };
ristretto255::CMasterSecret MasterSecret(DeriveStrongSecret(&text_secret[0]));
decltype(start_time) end_time{ ro::msec_since_epoch() };
wxLogMessage("\t\tStrong secret derivation took %d milliseconds", (end_time - start_time).count());
sql_update_to_misc update_to_misc(db);
update_to_misc(1, WALLET_FILE_IDENTIFIER);
update_to_misc(2, WALLET_FILE_SCHEMA_VERSION_0_0);
update_to_misc(3, &text_secret[0]);
update_to_misc(4, MasterSecret);
sql_insert_name insert_name(db);
const char* const cpsz("fred");
insert_name(cpsz, MasterSecret(cpsz).timesBase());
}
catch (const MyException& e) {
ILogError(R"|(Failed to create or failed to properly initialize wallet)|");
errorCode = 20;
szError = e.what();
ILogError(szError.c_str());
}
}
@ -339,7 +295,7 @@ void Frame::OnSaveNew(wxCommandEvent& WXUNUSED(event))
}
auto wallet{ dialog.GetDirectory() + "/" + dialog.GetFilename() };
wxLogMessage("new wallet created: %s", wallet);
wxConfigBase::Get()->Write("Wallet", wallet);
// wxConfigBase::Get()->Write("Wallet", wallet);
}
void Frame::OnFileOpen(wxCommandEvent&) {

View File

@ -77,7 +77,7 @@ private:
public:
void OnSaveNew(wxCommandEvent&);
void NewWalletNewSecret(wxCommandEvent&);
void NewWallet(wxFileName&, ristretto255::hash<256>&);
void RecreateWalletFromExistingSecret(wxCommandEvent&);
void OnFileOpen(wxCommandEvent&);

@ -1 +1 @@
Subproject commit 3033271213aa083b49ec88c23d815af176fc12a2
Subproject commit 561e556dad078af581f338fe3de9ee6362d28b16

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -62,7 +62,7 @@ constexpr bool b_WINDOWS = false;
static_assert(wxUSE_UNSAFE_WXSTRING_CONV == 1,
R"(In fully utf environment, (wallet.manifest plus
/utf-8 compile option) all string conversions are safe.)");
static_assert(wxMAJOR_VERSION == 3 && wxMINOR_VERSION == 1 && wxRELEASE_NUMBER == 5 && wxSUBRELEASE_NUMBER == 0, "expecting wxWidgets 3.1.5");
static_assert(wxMAJOR_VERSION == 3 && wxMINOR_VERSION == 1 && wxRELEASE_NUMBER == 6 && wxSUBRELEASE_NUMBER == 0, "expecting wxWidgets 3.1.5");
static_assert(wxUSE_IPV6 == 1, "IP6 unavailable in wxWidgets");
static_assert(WXWIN_COMPATIBILITY_3_0 == 0, "wxWidgets api out of date");
static_assert(wxUSE_COMPILER_TLS == (b_WINDOWS ? 2 : 1), "out of date workarounds in wxWidgets for windows bugs");

View File

@ -523,9 +523,9 @@ namespace ristretto255 {
}
}
if (!(singletonApp->m_quick_unit_test)){
ro::msec start_time{ ro::msec_since_epoch() };
auto start_time{ std::chrono::high_resolution_clock::now() };
auto s1{ DeriveStrongSecret(&text_secret[0]) };
decltype(start_time) end_time{ ro::msec_since_epoch() };
auto end_time{ std::chrono::high_resolution_clock::now() };
if (s1 == scalar({ 0x59, 0xf6, 0x73, 0xb4, 0xa0, 0xc7, 0x0d, 0x8b, 0x51, 0xe5, 0x87, 0x7c, 0xf5, 0xd7, 0x6f, 0x55, 0x31, 0xa7, 0x0b, 0x14, 0x28, 0x54, 0x97, 0x08, 0x9f, 0x27, 0x83, 0xe1, 0xc7, 0x5f, 0x55, 0x0f })) {
wxLogMessage("\t\tObtained expected strong scalar secret from scalar(7)");
}
@ -534,8 +534,9 @@ namespace ristretto255 {
szError = "Fail\tUnexpected strong scalar secret from text secret";
ILogError(szError.c_str());
}
wxLogMessage("\t\tStrong secret derivation took %d milliseconds", (end_time - start_time).count());
uintmax_t strengthening = (end_time - start_time).count() * shared_secrets_per_second / 1000;
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds> (end_time - start_time) };
wxLogMessage("\t\tStrong secret derivation took %lld microseconds", time_taken.count());
uintmax_t strengthening = time_taken.count() * shared_secrets_per_second / 1000000;
wxLogMessage("\t\tslows brute force password search by a factor of %ju", strengthening);
wxLogMessage("\t\tstrengthens password by %u bits",
static_cast<unsigned int>(rounded_log2(static_cast<uint32_t>(strengthening))));
@ -571,13 +572,13 @@ static bool TestShareSecretGenerationSpeed(void) {
ILogMessage("\tTest shared secret generation speed.");
try {
// throw MyException("fake failure to test failure handling");
unsigned int secrets{ 10*(3-2*debug_mode) };
unsigned int secrets{ 10 };
scalar a{ scalar::random() };
scalar b{ scalar::random() };
scalar c;
auto B{ a.timesBase() };
decltype(B) A, C;
ro::msec start_time{ ro::msec_since_epoch() };
auto start_time{ std::chrono::high_resolution_clock::now() };
for (unsigned int i{ secrets }; i; i--) {
c = a + b;
C = c * B;
@ -586,11 +587,12 @@ static bool TestShareSecretGenerationSpeed(void) {
b = c + a;
B = b * A;
}
decltype(start_time) end_time{ ro::msec_since_epoch() };
auto end_time{ std::chrono::high_resolution_clock::now() };
if (!B.valid()) { throw MyException("Unexpected invalid scalar"); }
wxLogMessage("\t\t%d shared secrets from public and private keys took %d milliseconds", secrets * 3, (end_time - start_time).count());
if ((end_time - start_time).count()) {
shared_secrets_per_second = secrets * 3000 / (end_time - start_time).count();
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds> (end_time - start_time) };
wxLogMessage("\t\t%d shared secrets from public and private keys took %lld microseconds", secrets * 3, time_taken.count());
if (time_taken.count()) {
shared_secrets_per_second = secrets * 3000000 / time_taken.count();
wxLogMessage("\t\t in one second, can generate %ju shared secrets",
shared_secrets_per_second
);
@ -624,11 +626,12 @@ static bool TestShareSecretGenerationSpeed(void) {
ptAnnPublicKey, // Signer's public key
szHello // Text to be signed.
);
ro::msec start_time{ ro::msec_since_epoch() };
auto start_time{ std::chrono::high_resolution_clock::now() };
if (sig.verify())
{
decltype(start_time) end_time{ ro::msec_since_epoch() };
wxLogMessage("\t\tverification of Schnorr signature took %d milliseconds", (end_time - start_time).count());
auto end_time{ std::chrono::high_resolution_clock::now() };
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds> (end_time - start_time) };
wxLogMessage("\t\tverification of Schnorr signature took %lld microseconds", time_taken.count());
ILogMessage("\t\tSchnorr Signature valid");
}
@ -760,7 +763,7 @@ static bool TestShareSecretGenerationSpeed(void) {
szError = "Fail\tInvalid test vectors.";
ILogError(szError.c_str());
}
ro::msec start_time{ ro::msec_since_epoch() };
auto start_time{ std::chrono::high_resolution_clock::now() };
auto sclr_a = scalar::random();
auto sclr_b = scalar::random();
if (sclr_a == sclr_b)throw MyException("random not very random");
@ -815,11 +818,11 @@ static bool TestShareSecretGenerationSpeed(void) {
}
else
{
decltype(start_time) end_time{ ro::msec_since_epoch() };
auto time_to_do_crypto{ end_time - start_time };
wxLogMessage(_T("\t\ttest of ristretto test vectors took %d milliseconds"), time_to_do_crypto.count());
auto end_time{ std::chrono::high_resolution_clock::now() };
auto time_to_do_crypto{ std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time) };
wxLogMessage(_T("\t\ttest of ristretto test vectors took %lld microseconds"), time_to_do_crypto.count());
ILogMessage("\tTesting generation of shared secrets.");
start_time = ro::msec_since_epoch();
start_time = std::chrono::high_resolution_clock::now();
scalar sclrAnonSessionSecretKey{ scalar::random() };
point ptAnonSessionPublicKey{ sclrAnonSessionSecretKey * point::ptBase };
@ -835,25 +838,27 @@ static bool TestShareSecretGenerationSpeed(void) {
ILogError(szError.c_str());
}
else {
end_time = ro::msec_since_epoch();
wxLogMessage(_wx("\tshared secret generation both ways and comparison took %d milliseconds"), (end_time - start_time).count());
end_time = std::chrono::high_resolution_clock::now();
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time) };
wxLogMessage(_wx("\tshared secret generation both ways and comparison took %lld microseconds"), time_taken.count());
}
ILogMessage("\tTesting hashing speed");
{
int hashes{ 20000 };
point sharedSecretKey = sclrAnonSessionSecretKey * ptServerPermanentPublicKey;
ServerCopyOfSharedSecret = hash<512>(sharedSecretKey, MessageTextToBeSecured);
start_time = ro::msec_since_epoch();
start_time = std::chrono::high_resolution_clock::now();
for (int i{ 0 }; i < hashes; i++) {
sharedSecretKey.blob[3] = ServerCopyOfSharedSecret.blob[5];
ServerCopyOfSharedSecret = hash<512>(sharedSecretKey, MessageTextToBeSecured);
}
end_time = ro::msec_since_epoch();
end_time = std::chrono::high_resolution_clock::now();
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time) };
{ bool f = false; //dont optimize pointless calculation
for (auto x : ServerCopyOfSharedSecret.blob) { f = f || x; }
if (f) {
wxLogMessage(_wx("\t\t%d hashes of pairs of shared secrets took %d milliseconds"), hashes, (end_time - start_time).count());
wxLogMessage(_wx("\t\tin one millisecond, can hash %d short messages"), hashes / (end_time - start_time).count());
wxLogMessage(_wx("\t\t%d hashes of pairs of shared secrets took %lld microseconds"), hashes, time_taken.count());
wxLogMessage(_wx("\t\tin one millisecond, can hash %lld short messages"), 1000LL*hashes / time_taken.count());
}
}
}
@ -864,31 +869,33 @@ static bool TestShareSecretGenerationSpeed(void) {
hash c(b);
if(hash(b, c) != hash(hsh() << b << c)) throw MyException("inconsistent hashes generated");
constexpr int hashes{ 3000 };
start_time = ro::msec_since_epoch();
start_time = std::chrono::high_resolution_clock::now();
for (int i{ 0 }; i < hashes; i++) {
a = hash(b, c);
b = hash(c, a);
c = hash(a, b);
}
end_time = ro::msec_since_epoch();
end_time = std::chrono::high_resolution_clock::now();
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds> (end_time - start_time) };
{ bool f = false; //dont optimize pointless calculation away
for (auto x : c.blob) { f = f || x; }
if (f) {
wxLogMessage(_wx("\t\t%d hashes of a pair of hashes took %d milliseconds"), hashes * 3, (end_time - start_time).count());
if ((end_time - start_time).count() > 0) {
wxLogMessage(_wx("\t\tin one millisecond, can hash %d pairs of 256 bit hashes"), hashes * 3 / (end_time - start_time).count());
wxLogMessage(_wx("\t\t%d hashes of a pair of hashes took %lld microseconds"), hashes * 3, time_taken.count());
if (time_taken.count() > 0) {
wxLogMessage(_wx("\t\tin one millisecond, can hash %lld pairs of 256 bit hashes"), hashes * 3000ll / time_taken.count());
}
}
}
uint8_t buff[hashes * 3 * 64];
randombytes_buf(std::span<byte>(buff));
start_time = ro::msec_since_epoch();
start_time = std::chrono::high_resolution_clock::now();
c = hash(buff);
end_time = ro::msec_since_epoch();
end_time = std::chrono::high_resolution_clock::now();
time_taken = std::chrono::duration_cast<std::chrono::microseconds> (end_time - start_time);
{ bool f = false; //dont optimize pointless calculation away
for (auto x : c.blob) { f = f || x; }
if (f) {
wxLogMessage(_wx("\t\tone hash of %d bytes took %d milliseconds"), hashes * 3 * 64, (end_time - start_time).count());
wxLogMessage(_wx("\t\tone hash of %d bytes took %lld microseconds"), hashes * 3 * 64, time_taken.count());
}
}
}

View File

@ -132,7 +132,7 @@
<ClInclude Include="rotime.h" />
<ClInclude Include="secrets.h" />
<ClInclude Include="slash6.h" />
<ClInclude Include="sqlite3.h" />
<ClInclude Include="sqlite3/sqlite3.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="welcome_to_rhocoin.h" />
</ItemGroup>
@ -157,7 +157,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="sqlite3.c">
<ClCompile Include="sqlite3/sqlite3.c">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>

View File

@ -115,6 +115,13 @@ echo on
.\x64\Debug\wallet.exe --complete --test
echo off
IF %ERRORLEVEL% NEQ 0 (
echo failed unit test
echo failed unit test on debug build
) ELSE (
echo passed unit test)
echo passed unit test on debug build)
echo on
.\x64\Release\wallet.exe --complete --test
echo off
IF %ERRORLEVEL% NEQ 0 (
echo failed unit test on release build
) ELSE (
echo passed unit test on release build)

@ -1 +1 @@
Subproject commit 9c0a8be1dc32063d91ed1901fd5fcd54f4f955a1
Subproject commit 35a6d7b15fedfdb5198bb6c28b31cda33b2c2a76