From 3e82f1aa99cff8eb521f1a3d725fa1e7ff8fdcfb Mon Sep 17 00:00:00 2001 From: IngCr3at1on Date: Thu, 7 Jul 2016 13:27:46 -0500 Subject: [PATCH 1/4] Build tests Only tested makefile.unix (on linux system) Does not address failing tests, only makes them compile. Several test files are disabled cause they broke the build, will require further investigation. --- .gitignore | 2 + src/makefile.bsd | 21 ++++++- src/makefile.linux-mingw | 12 ++++ src/makefile.mingw | 12 +++- src/makefile.osx | 24 +++++++- src/makefile.unix | 22 ++++++- ...ts_tests.cpp => Checkpoints_tests.cpp.bak} | 0 ...ing_tests.cpp => accounting_tests.cpp.bak} | 0 ...{base58_tests.cpp => base58_tests.cpp.bak} | 0 ...{bignum_tests.cpp => bignum_tests.cpp.bak} | 0 ...lize_tests.cpp => serialize_tests.cpp.bak} | 0 src/test/test_ion.cpp | 57 +++++++++++++++++++ ...int160_tests.cpp => uint160_tests.cpp.bak} | 0 ...int256_tests.cpp => uint256_tests.cpp.bak} | 0 ...{wallet_tests.cpp => wallet_tests.cpp.bak} | 0 15 files changed, 146 insertions(+), 4 deletions(-) rename src/test/{Checkpoints_tests.cpp => Checkpoints_tests.cpp.bak} (100%) rename src/test/{accounting_tests.cpp => accounting_tests.cpp.bak} (100%) rename src/test/{base58_tests.cpp => base58_tests.cpp.bak} (100%) rename src/test/{bignum_tests.cpp => bignum_tests.cpp.bak} (100%) rename src/test/{serialize_tests.cpp => serialize_tests.cpp.bak} (100%) create mode 100644 src/test/test_ion.cpp rename src/test/{uint160_tests.cpp => uint160_tests.cpp.bak} (100%) rename src/test/{uint256_tests.cpp => uint256_tests.cpp.bak} (100%) rename src/test/{wallet_tests.cpp => wallet_tests.cpp.bak} (100%) diff --git a/.gitignore b/.gitignore index 3d0d59b..81c58e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.tar.gz *.exe +iond +test_ion # Compilation and Qt preprocessor part *.qm diff --git a/src/makefile.bsd b/src/makefile.bsd index edbeebd..7df6a09 100644 --- a/src/makefile.bsd +++ b/src/makefile.bsd @@ -12,6 +12,8 @@ DEFS=-DBOOST_SPIRIT_THREADSAFE DEFS += $(addprefix -I,$(CURDIR) $(CURDIR)/obj $(BOOST_INCLUDE_PATH) $(BDB_INCLUDE_PATH) $(OPENSSL_INCLUDE_PATH)) LIBS = $(addprefix -L,$(BOOST_LIB_PATH) $(BDB_LIB_PATH) $(OPENSSL_LIB_PATH)) +TESTDEFS = -DTEST_DATA_DIR=$(abspath test/data) + LMODE = dynamic LMODE2 = dynamic ifdef STATIC @@ -19,6 +21,8 @@ ifdef STATIC ifeq (${STATIC}, all) LMODE2 = static endif +else + TESTDEFS += -DBOOST_TEST_DYN_LINK endif # for boost 1.37, add -mt to the boost libraries @@ -179,10 +183,25 @@ obj/%.o: %.cpp iond: $(OBJS:obj/%=obj/%) $(LINK) $(xCXXFLAGS) -o $@ $^ $(xLDFLAGS) $(LIBS) +TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) + +obj-test/%.o: test/%.cpp + $(CXX) -c $(TESTDEFS) $(xCXXFLAGS) -MMD -o $@ $< + @cp $(@:%.o=%.d) $(@:%.o=%.P); \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ + rm -f $(@:%.o=%.d) + + +test_ion: $(TESTOBJS) $(filter-out obj/init.o obj/bitcoin.o,$(OBJS:obj/%=obj/%)) + $(CXX) $(xCXXFLAGS) -o $@ $(LIBPATHS) $^ -Wl,-B$(LMODE) -lboost_unit_test_framework $(xLDFLAGS) $(LIBS) + clean: - -rm -f iond + -rm -f iond test_ion -rm -f obj/*.o + -rm -f obj-test/*.o -rm -f obj/*.P + -rm -f obj-test/*.P -rm -f obj/build.h FORCE: diff --git a/src/makefile.linux-mingw b/src/makefile.linux-mingw index 2157cf9..84cf8d6 100644 --- a/src/makefile.linux-mingw +++ b/src/makefile.linux-mingw @@ -43,6 +43,8 @@ DEBUGFLAGS=-g CFLAGS=-O2 -msse2 -w -Wall -Wextra -Wno-ignored-qualifiers -Wformat -Wformat-security -Wno-unused-parameter $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS) LDFLAGS=-Wl,--dynamicbase -Wl,--nxcompat -static-libgcc -static-libstdc++ +TESTDEFS = -DTEST_DATA_DIR=$(abspath test/data) + ifndef USE_UPNP override USE_UPNP = - endif @@ -133,9 +135,19 @@ obj/scrypt-x86.o: scrypt-x86.S obj/scrypt-x86_64.o: scrypt-x86_64.S $(CXX) -c $(CFLAGS) -MMD -o $@ $< +TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) + +obj-test/%.o: test/%.cpp $(HEADERS) + ${HOST}-g++ -c $(TESTDEFS) $(CFLAGS) -o $@ $< + +test_ion.exe: $(TESTOBJS) $(filter-out obj/init.o obj/bitcoin.o,$(OBJS:obj/%=obj/%)) + ${HOST}-g++ $(CFLAGS) -o $@ $(LIBPATHS) $^ -lboost_unit_test_framework $(LIBS) + clean: -rm -f obj/*.o -rm -f iond.exe + -rm -f obj-test/*.o + -rm -f test_paycoin.exe -rm -f obj/build.h cd leveldb && TARGET_OS=OS_WINDOWS_CROSSCOMPILE $(MAKE) clean && cd .. diff --git a/src/makefile.mingw b/src/makefile.mingw index f805d7e..6d7865b 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -117,8 +117,18 @@ obj/scrypt-x86_64.o: scrypt-x86_64.S iond.exe: $(OBJS:obj/%=obj/%) g++ $(CFLAGS) $(LDFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) +TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) + +obj-test/%.o: test/%.cpp $(HEADERS) + g++ -c $(TESTDEFS) $(CFLAGS) -o $@ $< + +test_ion.exe: $(TESTOBJS) $(filter-out obj/init.o obj/bitcoin.o,$(OBJS:obj/%=obj/%)) + g++ $(CFLAGS) -o $@ $(LIBPATHS) $^ -lboost_unit_test_framework $(LIBS) + clean: - -del /Q iond + -del /Q iond test_ion -del /Q obj\* + -del /Q obj-test\* + -del /Q build.h FORCE: diff --git a/src/makefile.osx b/src/makefile.osx index 7864313..d6ca819 100644 --- a/src/makefile.osx +++ b/src/makefile.osx @@ -24,8 +24,12 @@ USE_WALLET:=1 LIBS= -dead_strip +TESTDEFS = -DTEST_DATA_DIR=$(abspath test/data) + ifdef STATIC # Build STATIC if you are redistributing the iond binary +TESTLIBS += \ + $(DEPSDIR)/lib/libboost_unit_test_framework-mt.a LIBS += \ $(DEPSDIR)/lib/db48/libdb_cxx-4.8.a \ $(DEPSDIR)/lib/libboost_system-mt.a \ @@ -36,6 +40,8 @@ LIBS += \ $(DEPSDIR)/lib/libcrypto.a \ -lz else +TESTLIBS += \ + -lboost_unit_test_framework-mt LIBS += \ -ldb_cxx-4.8 \ -lboost_system-mt \ @@ -45,6 +51,7 @@ LIBS += \ -lssl \ -lcrypto \ -lz +TESTDEFS += -DBOOST_TEST_DYN_LINK endif DEFS=-DMAC_OSX -DMSG_NOSIGNAL=0 -DBOOST_SPIRIT_THREADSAFE @@ -134,6 +141,7 @@ obj/txdb-leveldb.o: leveldb/libleveldb.a # auto-generated dependencies: -include obj/*.P +-include obj-test/*.P obj/build.h: FORCE /bin/sh ../share/genbuild.sh obj/build.h @@ -156,10 +164,24 @@ obj/scrypt-x86_64.o: scrypt-x86_64.S iond: $(OBJS:obj/%=obj/%) $(CXX) $(CFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) +TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) + +obj-test/%.o: test/%.cpp + $(CXX) -c $(TESTDEFS) $(CFLAGS) -MMD -o $@ $< + @cp $(@:%.o=%.d) $(@:%.o=%.P); \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ + rm -f $(@:%.o=%.d) + +test_ion: $(TESTOBJS) $(filter-out obj/init.o obj/bitcoin.o,$(OBJS:obj/%=obj/%)) + $(CXX) $(CFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) $(TESTLIBS) + clean: - -rm -f iond + -rm -f iond test_ion -rm -f obj/*.o + -rm -f obj-test/*.o -rm -f obj/*.P + -rm -f obj-test/*.P -rm -f obj/build.h FORCE: diff --git a/src/makefile.unix b/src/makefile.unix index 4408864..c5b38be 100644 --- a/src/makefile.unix +++ b/src/makefile.unix @@ -13,6 +13,8 @@ DEFS += -DUSE_SECP256K1 DEFS += $(addprefix -I,$(CURDIR) $(CURDIR)/obj $(BOOST_INCLUDE_PATH) $(BDB_INCLUDE_PATH) $(OPENSSL_INCLUDE_PATH)) LIBS = $(addprefix -L,$(BOOST_LIB_PATH) $(BDB_LIB_PATH) $(OPENSSL_LIB_PATH)) +TESTDEFS = -DTEST_DATA_DIR=$(abspath test/data) + LMODE = dynamic LMODE2 = dynamic ifdef STATIC @@ -20,6 +22,8 @@ ifdef STATIC ifeq (${STATIC}, all) LMODE2 = static endif +else + TESTDEFS += -DBOOST_TEST_DYN_LINK endif LIBS += \ @@ -186,6 +190,7 @@ obj/txdb-leveldb.o: leveldb/libleveldb.a # auto-generated dependencies: -include obj/*.P +-include obj-test/*.P obj/build.h: FORCE /bin/sh ../share/genbuild.sh obj/build.h @@ -211,10 +216,25 @@ obj/%.o: %.cpp iond: $(OBJS:obj/%=obj/%) $(LINK) $(xCXXFLAGS) -o $@ $^ $(xLDFLAGS) $(LIBS) +TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) + +obj-test/%.o: test/%.cpp + $(CXX) -c $(TESTDEFS) $(xCXXFLAGS) -MMD -o $@ $< + @cp $(@:%.o=%.d) $(@:%.o=%.P); \ + sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \ + -e '/^$$/ d' -e 's/$$/ :/' < $(@:%.o=%.d) >> $(@:%.o=%.P); \ + rm -f $(@:%.o=%.d) + + +test_ion: $(TESTOBJS) $(filter-out obj/init.o obj/bitcoind.o,$(OBJS:obj/%=obj/%)) + $(CXX) $(xCXXFLAGS) -o $@ $(LIBPATHS) $^ -Wl,-B$(LMODE) -lboost_unit_test_framework $(xLDFLAGS) $(LIBS) + clean: - -rm -f iond + -rm -f iond test_ion -rm -f obj/*.o + -rm -f obj-test/*.o -rm -f obj/*.P + -rm -f obj-test/*.P -rm -f obj/build.h FORCE: diff --git a/src/test/Checkpoints_tests.cpp b/src/test/Checkpoints_tests.cpp.bak similarity index 100% rename from src/test/Checkpoints_tests.cpp rename to src/test/Checkpoints_tests.cpp.bak diff --git a/src/test/accounting_tests.cpp b/src/test/accounting_tests.cpp.bak similarity index 100% rename from src/test/accounting_tests.cpp rename to src/test/accounting_tests.cpp.bak diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp.bak similarity index 100% rename from src/test/base58_tests.cpp rename to src/test/base58_tests.cpp.bak diff --git a/src/test/bignum_tests.cpp b/src/test/bignum_tests.cpp.bak similarity index 100% rename from src/test/bignum_tests.cpp rename to src/test/bignum_tests.cpp.bak diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp.bak similarity index 100% rename from src/test/serialize_tests.cpp rename to src/test/serialize_tests.cpp.bak diff --git a/src/test/test_ion.cpp b/src/test/test_ion.cpp new file mode 100644 index 0000000..cb1c64c --- /dev/null +++ b/src/test/test_ion.cpp @@ -0,0 +1,57 @@ +#define BOOST_TEST_MODULE Ion Test Suite +#include + +#include "main.h" +#include "wallet.h" + +int MIN_PROTO_VERSION = 60027; + +CWallet* pwalletMain; +CClientUIInterface uiInterface; + +bool fConfChange; +bool fMinimizeCoinAge; +unsigned int nNodeLifespan; +unsigned int nDerivationMethodIndex; +unsigned int nMinerSleep; +bool fUseFastIndex; + +extern bool fPrintToConsole; +struct TestingSetup { + TestingSetup() { + fPrintToConsole = true; // don't want to write to debug.log file + pwalletMain = new CWallet(); + bitdb.MakeMock(); + LoadBlockIndex(true); + bool fFirstRun; + pwalletMain = new CWallet("wallet.dat"); + pwalletMain->LoadWallet(fFirstRun); + RegisterWallet(pwalletMain); + } + ~TestingSetup() + { + delete pwalletMain; + pwalletMain = NULL; + bitdb.Flush(true); + } +}; + +BOOST_GLOBAL_FIXTURE(TestingSetup); + +volatile bool fRequestShutdown = false; + +void Shutdown(void* parg) +{ + exit(0); +} + +void StartShutdown() +{ + fRequestShutdown = true; + exit(0); +} + +bool ShutdownRequested() +{ + return fRequestShutdown; +} diff --git a/src/test/uint160_tests.cpp b/src/test/uint160_tests.cpp.bak similarity index 100% rename from src/test/uint160_tests.cpp rename to src/test/uint160_tests.cpp.bak diff --git a/src/test/uint256_tests.cpp b/src/test/uint256_tests.cpp.bak similarity index 100% rename from src/test/uint256_tests.cpp rename to src/test/uint256_tests.cpp.bak diff --git a/src/test/wallet_tests.cpp b/src/test/wallet_tests.cpp.bak similarity index 100% rename from src/test/wallet_tests.cpp rename to src/test/wallet_tests.cpp.bak From e3d3c7d1418ee263ff1ed0871e29a4246abc88b2 Mon Sep 17 00:00:00 2001 From: Mitchell Cash Date: Sat, 9 Jul 2016 09:36:16 +1000 Subject: [PATCH 2/4] Fix missing separator issue --- src/makefile.linux-mingw | 2 +- src/makefile.mingw | 4 ++-- src/makefile.osx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/makefile.linux-mingw b/src/makefile.linux-mingw index 84cf8d6..453fcb4 100644 --- a/src/makefile.linux-mingw +++ b/src/makefile.linux-mingw @@ -146,7 +146,7 @@ test_ion.exe: $(TESTOBJS) $(filter-out obj/init.o obj/bitcoin.o,$(OBJS:obj/%=obj clean: -rm -f obj/*.o -rm -f iond.exe - -rm -f obj-test/*.o + -rm -f obj-test/*.o -rm -f test_paycoin.exe -rm -f obj/build.h cd leveldb && TARGET_OS=OS_WINDOWS_CROSSCOMPILE $(MAKE) clean && cd .. diff --git a/src/makefile.mingw b/src/makefile.mingw index 6d7865b..ede6e24 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -128,7 +128,7 @@ test_ion.exe: $(TESTOBJS) $(filter-out obj/init.o obj/bitcoin.o,$(OBJS:obj/%=obj clean: -del /Q iond test_ion -del /Q obj\* - -del /Q obj-test\* - -del /Q build.h + -del /Q obj-test\* + -del /Q build.h FORCE: diff --git a/src/makefile.osx b/src/makefile.osx index d6ca819..e5b6034 100644 --- a/src/makefile.osx +++ b/src/makefile.osx @@ -179,9 +179,9 @@ test_ion: $(TESTOBJS) $(filter-out obj/init.o obj/bitcoin.o,$(OBJS:obj/%=obj/%)) clean: -rm -f iond test_ion -rm -f obj/*.o - -rm -f obj-test/*.o + -rm -f obj-test/*.o -rm -f obj/*.P - -rm -f obj-test/*.P + -rm -f obj-test/*.P -rm -f obj/build.h FORCE: From c2cdb9b9a1965066b04c0a87704f43843a00dd1e Mon Sep 17 00:00:00 2001 From: IngCr3at1on Date: Sun, 26 Jun 2016 15:47:05 -0500 Subject: [PATCH 3/4] Implement core scrape address functionality --- src/rpcclient.cpp | 5 + src/rpcclient.h | 4 + src/rpcserver.cpp | 12 +- src/rpcserver.h | 6 + src/rpcwallet.cpp | 284 +++++++++++++++++++++++++----------- src/test/rpc_tests.cpp | 193 +++++++++++++++++++++++++ src/wallet.cpp | 316 ++++++++++++++++++++++------------------- src/wallet.h | 36 ++++- src/walletdb.cpp | 69 ++++++++- src/walletdb.h | 14 +- 10 files changed, 696 insertions(+), 243 deletions(-) create mode 100644 src/test/rpc_tests.cpp diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index 7b4358a..df02eb5 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2013 The Bitcoin developers +// Copyright (c) 2016 Nathan Bass "IngCr3at1on" // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -157,6 +158,10 @@ static const CRPCConvertParam vRPCConvertParams[] = { "searchrawtransactions", 1 }, { "searchrawtransactions", 2 }, { "searchrawtransactions", 3 }, + { "getscrapeaddress", 1 }, + { "setscrapeaddress", 2 }, + { "listscrapeaddresses", 0 }, + { "deletescrapeaddress", 1 }, }; class CRPCConvertTable diff --git a/src/rpcclient.h b/src/rpcclient.h index f3ea56c..73ed13a 100644 --- a/src/rpcclient.h +++ b/src/rpcclient.h @@ -1,5 +1,6 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2013 The Bitcoin developers +// Copyright (c) 2016 Nathan Bass "IngCr3at1on" // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -14,4 +15,7 @@ int CommandLineRPC(int argc, char *argv[]); json_spirit::Array RPCConvertValues(const std::string &strMethod, const std::vector &strParams); +/** Call the RPC service directly (placed here to allow for more in depth tests.) */ +json_spirit::Object CallRPC(const std::string& strMethod, const json_spirit::Array& params); + #endif diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index cdbf760..87f43d4 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -303,10 +303,14 @@ static const CRPCCommand vRPCCommands[] = { "resendtx", &resendtx, false, true, true }, { "makekeypair", &makekeypair, false, true, false }, { "checkkernel", &checkkernel, true, false, true }, - { "getnewstealthaddress", &getnewstealthaddress, false, false, true}, - { "liststealthaddresses", &liststealthaddresses, false, false, true}, - { "importstealthaddress", &importstealthaddress, false, false, true}, - { "sendtostealthaddress", &sendtostealthaddress, false, false, true}, + { "getnewstealthaddress", &getnewstealthaddress, false, false, true }, + { "liststealthaddresses", &liststealthaddresses, false, false, true }, + { "importstealthaddress", &importstealthaddress, false, false, true }, + { "sendtostealthaddress", &sendtostealthaddress, false, false, true }, + { "setscrapeaddress", &setscrapeaddress, false, false, true }, + { "getscrapeaddress", &getscrapeaddress, false, false, true }, + { "listscrapeaddresses", &listscrapeaddresses, false, false, true }, + { "deletescrapeaddress", &deletescrapeaddress, false, false, true } #endif }; diff --git a/src/rpcserver.h b/src/rpcserver.h index 42aa6fe..b6fddfc 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -1,5 +1,6 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2016 Nathan Bass "IngCr3at1on" // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -189,4 +190,9 @@ extern json_spirit::Value darksend(const json_spirit::Array& params, bool fHelp) extern json_spirit::Value spork(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value masternode(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value setscrapeaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getscrapeaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value listscrapeaddresses(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value deletescrapeaddress(const json_spirit::Array& params, bool fHelp); + #endif diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index c50e1cd..71c7979 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2016 Nathan Bass "IngCr3at1on" // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -278,7 +279,7 @@ Value sendtoaddress(const Array& params, bool fHelp) std::string sNarr; if (params.size() > 4 && params[4].type() != null_type && !params[4].get_str().empty()) sNarr = params[4].get_str(); - + if (sNarr.length() > 24) throw runtime_error("Narration must be 24 characters or less."); @@ -616,7 +617,7 @@ Value sendfrom(const Array& params, bool fHelp) std::string sNarr; if (params.size() > 6 && params[6].type() != null_type && !params[6].get_str().empty()) sNarr = params[6].get_str(); - + if (sNarr.length() > 24) throw runtime_error("Narration must be 24 characters or less."); @@ -1570,7 +1571,7 @@ Value makekeypair(const Array& params, bool fHelp) string strPrefix = ""; if (params.size() > 0) strPrefix = params[0].get_str(); - + CKey key; key.MakeNewKey(false); @@ -1601,22 +1602,22 @@ Value getnewstealthaddress(const Array& params, bool fHelp) throw runtime_error( "getnewstealthaddress [label]\n" "Returns a new ion stealth address for receiving payments anonymously. "); - + if (pwalletMain->IsLocked()) throw runtime_error("Failed: Wallet must be unlocked."); - + std::string sLabel; if (params.size() > 0) sLabel = params[0].get_str(); - + CStealthAddress sxAddr; std::string sError; if (!pwalletMain->NewStealthAddress(sError, sLabel, sxAddr)) throw runtime_error(std::string("Could get new stealth address: ") + sError); - + if (!pwalletMain->AddStealthAddress(sxAddr)) throw runtime_error("Could not save to wallet."); - + return sxAddr.Encoded(); } @@ -1626,27 +1627,27 @@ Value liststealthaddresses(const Array& params, bool fHelp) throw runtime_error( "liststealthaddresses [show_secrets=0]\n" "List owned stealth addresses."); - + bool fShowSecrets = false; - + if (params.size() > 0) { std::string str = params[0].get_str(); - + if (str == "0" || str == "n" || str == "no" || str == "-" || str == "false") fShowSecrets = false; else fShowSecrets = true; }; - + if (fShowSecrets) { if (pwalletMain->IsLocked()) throw runtime_error("Failed: Wallet must be unlocked."); }; - + Object result; - + //std::set::iterator it; //for (it = pwalletMain->stealthAddresses.begin(); it != pwalletMain->stealthAddresses.end(); ++it) BOOST_FOREACH(CStealthAddress sit, pwalletMain->stealthAddresses) @@ -1654,7 +1655,7 @@ Value liststealthaddresses(const Array& params, bool fHelp) CStealthAddress* it = &(sit); if (it->scan_secret.size() < 1) continue; // stealth address is not owned - + if (fShowSecrets) { Object objA; @@ -1668,7 +1669,7 @@ Value liststealthaddresses(const Array& params, bool fHelp) result.push_back(Pair("Stealth Address", it->Encoded() + " - " + it->label)); }; }; - + return result; } @@ -1678,20 +1679,20 @@ Value importstealthaddress(const Array& params, bool fHelp) throw runtime_error( "importstealthaddress [label]\n" "Import an owned stealth addresses."); - + std::string sScanSecret = params[0].get_str(); std::string sSpendSecret = params[1].get_str(); std::string sLabel; - - + + if (params.size() > 2) { sLabel = params[2].get_str(); }; - + std::vector vchScanSecret; std::vector vchSpendSecret; - + if (IsHex(sScanSecret)) { vchScanSecret = ParseHex(sScanSecret); @@ -1700,7 +1701,7 @@ Value importstealthaddress(const Array& params, bool fHelp) if (!DecodeBase58(sScanSecret, vchScanSecret)) throw runtime_error("Could not decode scan secret as hex or base58."); }; - + if (IsHex(sSpendSecret)) { vchSpendSecret = ParseHex(sSpendSecret); @@ -1709,35 +1710,35 @@ Value importstealthaddress(const Array& params, bool fHelp) if (!DecodeBase58(sSpendSecret, vchSpendSecret)) throw runtime_error("Could not decode spend secret as hex or base58."); }; - + if (vchScanSecret.size() != 32) throw runtime_error("Scan secret is not 32 bytes."); if (vchSpendSecret.size() != 32) throw runtime_error("Spend secret is not 32 bytes."); - - + + ec_secret scan_secret; ec_secret spend_secret; - + memcpy(&scan_secret.e[0], &vchScanSecret[0], 32); memcpy(&spend_secret.e[0], &vchSpendSecret[0], 32); - + ec_point scan_pubkey, spend_pubkey; if (SecretToPublicKey(scan_secret, scan_pubkey) != 0) throw runtime_error("Could not get scan public key."); - + if (SecretToPublicKey(spend_secret, spend_pubkey) != 0) throw runtime_error("Could not get spend public key."); - - + + CStealthAddress sxAddr; sxAddr.label = sLabel; sxAddr.scan_pubkey = scan_pubkey; sxAddr.spend_pubkey = spend_pubkey; - + sxAddr.scan_secret = vchScanSecret; sxAddr.spend_secret = vchSpendSecret; - + Object result; bool fFound = false; // -- find if address already exists @@ -1756,12 +1757,12 @@ Value importstealthaddress(const Array& params, bool fHelp) fFound = true; // update stealth address with secrets break; }; - + result.push_back(Pair("result", "Import failed - stealth address exists.")); return result; }; }; - + if (fFound) { result.push_back(Pair("result", "Success, updated " + sxAddr.Encoded())); @@ -1770,11 +1771,11 @@ Value importstealthaddress(const Array& params, bool fHelp) pwalletMain->stealthAddresses.insert(sxAddr); result.push_back(Pair("result", "Success, imported " + sxAddr.Encoded())); }; - - + + if (!pwalletMain->AddStealthAddress(sxAddr)) throw runtime_error("Could not save to wallet."); - + return result; } @@ -1786,44 +1787,44 @@ Value sendtostealthaddress(const Array& params, bool fHelp) "sendtostealthaddress [comment] [comment-to] [narration]\n" " is a real and is rounded to the nearest 0.000001" + HelpRequiringPassphrase()); - + if (pwalletMain->IsLocked()) throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); - + std::string sEncoded = params[0].get_str(); int64_t nAmount = AmountFromValue(params[1]); - + std::string sNarr; if (params.size() > 4 && params[4].type() != null_type && !params[4].get_str().empty()) sNarr = params[4].get_str(); - + if (sNarr.length() > 24) throw runtime_error("Narration must be 24 characters or less."); - + CStealthAddress sxAddr; Object result; - + if (!sxAddr.SetEncoded(sEncoded)) { result.push_back(Pair("result", "Invalid ion stealth address.")); return result; }; - - + + CWalletTx wtx; if (params.size() > 2 && params[2].type() != null_type && !params[2].get_str().empty()) wtx.mapValue["comment"] = params[2].get_str(); if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) wtx.mapValue["to"] = params[3].get_str(); - + std::string sError; if (!pwalletMain->SendStealthMoneyToDestination(sxAddr, nAmount, sNarr, wtx, sError)) throw JSONRPCError(RPC_WALLET_ERROR, sError); return wtx.GetHash().GetHex(); - + result.push_back(Pair("result", "Not implemented yet.")); - + return result; } @@ -1833,17 +1834,17 @@ Value scanforalltxns(const Array& params, bool fHelp) throw runtime_error( "scanforalltxns [fromHeight]\n" "Scan blockchain for owned transactions."); - + Object result; int32_t nFromHeight = 0; - + CBlockIndex *pindex = pindexGenesisBlock; - - + + if (params.size() > 0) nFromHeight = params[0].get_int(); - - + + if (nFromHeight > 0) { pindex = mapBlockIndex[hashBestChain]; @@ -1851,21 +1852,21 @@ Value scanforalltxns(const Array& params, bool fHelp) && pindex->pprev) pindex = pindex->pprev; }; - + if (pindex == NULL) throw runtime_error("Genesis Block is not set."); - + { LOCK2(cs_main, pwalletMain->cs_wallet); - + pwalletMain->MarkDirty(); - + pwalletMain->ScanForWalletTransactions(pindex, true); pwalletMain->ReacceptWalletTransactions(); } - + result.push_back(Pair("result", "Scan complete.")); - + return result; } @@ -1875,19 +1876,19 @@ Value scanforstealthtxns(const Array& params, bool fHelp) throw runtime_error( "scanforstealthtxns [fromHeight]\n" "Scan blockchain for owned stealth transactions."); - + Object result; uint32_t nBlocks = 0; uint32_t nTransactions = 0; int32_t nFromHeight = 0; - + CBlockIndex *pindex = pindexGenesisBlock; - - + + if (params.size() > 0) nFromHeight = params[0].get_int(); - - + + if (nFromHeight > 0) { pindex = mapBlockIndex[hashBestChain]; @@ -1895,44 +1896,44 @@ Value scanforstealthtxns(const Array& params, bool fHelp) && pindex->pprev) pindex = pindex->pprev; }; - + if (pindex == NULL) throw runtime_error("Genesis Block is not set."); - + // -- locks in AddToWalletIfInvolvingMe - + bool fUpdate = true; // todo: option? - + pwalletMain->nStealth = 0; pwalletMain->nFoundStealth = 0; - + while (pindex) { nBlocks++; CBlock block; block.ReadFromDisk(pindex, true); - + BOOST_FOREACH(CTransaction& tx, block.vtx) { - + nTransactions++; - + pwalletMain->AddToWalletIfInvolvingMe(tx, &block, fUpdate); }; - + pindex = pindex->pnext; }; - + printf("Scanned %u blocks, %u transactions\n", nBlocks, nTransactions); printf("Found %u stealth transactions in blockchain.\n", pwalletMain->nStealth); printf("Found %u new owned stealth transactions.\n", pwalletMain->nFoundStealth); - + char cbuf[256]; snprintf(cbuf, sizeof(cbuf), "%u new stealth transactions.", pwalletMain->nFoundStealth); - + result.push_back(Pair("result", "Scan complete.")); result.push_back(Pair("found", std::string(cbuf))); - + return result; } @@ -1988,3 +1989,128 @@ Value keepass(const Array& params, bool fHelp) { return "Invalid command"; } + +Value setscrapeaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 2) { + string ret = "setscrapeaddress ,
\nSet an auto scrape address to send stake rewards to from a given address."; + if (pwalletMain->IsCrypted()) + ret += "requires wallet passphrase to be set with walletpassphrase first"; + + throw runtime_error(ret); + } + + EnsureWalletIsUnlocked(); + + string strAddress = params[0].get_str(); + CBitcoinAddress address(strAddress); + string strScrapeAddress = params[1].get_str(); + CBitcoinAddress scrapeAddress(strScrapeAddress); + + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address."); + + if (address.Get() == scrapeAddress.Get()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot set scrape address to the same as staking address."); + + if (!IsMine(*pwalletMain, address.Get())) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Staking address must be in wallet."); + + if (!scrapeAddress.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scrape address."); + + string oldScrapeAddress; + bool warn = false; + if (pwalletMain->ReadScrapeAddress(strAddress, oldScrapeAddress)) { + if (strScrapeAddress == oldScrapeAddress) + throw runtime_error(strprintf("Scrape address is already set to %s", oldScrapeAddress.c_str())); + + warn = true; + } + + if (pwalletMain->WriteScrapeAddress(strAddress, strScrapeAddress)) { + if (warn) + return strprintf("Warning overwriting %s with %s", oldScrapeAddress.c_str(), strScrapeAddress.c_str()); + + Object obj; + obj.push_back(Pair(strAddress, strScrapeAddress)); + return obj; + } + + // THis should never happen. + throw JSONRPCError(-1, "setscrapeaddress: unknown error"); +} + +Value getscrapeaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "getscrapeaddress \n" + "Get the auto scrape address for a given address." + ); + + string strAddress = params[0].get_str(); + CBitcoinAddress address(strAddress); + + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address."); + + if (!IsMine(*pwalletMain, address.Get())) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Staking address must be in wallet."); + + string strScrapeAddress; + if (!pwalletMain->ReadScrapeAddress(strAddress, strScrapeAddress)) { + string ret = "No scrape address set for address "; + ret += strAddress; + throw JSONRPCError(RPC_WALLET_ERROR, ret); + } + + Object obj; + obj.push_back(Pair(strAddress, strScrapeAddress)); + return obj; +} + +Value listscrapeaddresses(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "listscrapeaddresses\n" + "List all the defined scrape addresses." + ); + + Object obj; + LOCK(pwalletMain->cs_wallet); + CWalletDB(pwalletMain->strWalletFile).DumpScrapeAddresses(obj); + + return obj; +} + +Value deletescrapeaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 1) { + string ret = "deletescrapeaddress \nDelete the auto scrape address for a given address."; + if (pwalletMain->IsCrypted()) + ret += "requires wallet passphrase to be set with walletpassphrase first"; + + throw runtime_error(ret); + } + + EnsureWalletIsUnlocked(); + + string strAddress = params[0].get_str(); + CBitcoinAddress address(strAddress); + + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address."); + + if (!IsMine(*pwalletMain, address.Get())) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Staking address must be in wallet."); + + if (!pwalletMain->HasScrapeAddress(strAddress)) { + string ret = "No scrape address set for address "; + ret += strAddress; + throw JSONRPCError(RPC_WALLET_ERROR, ret); + } + + return pwalletMain->EraseScrapeAddress(strAddress); +} diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp new file mode 100644 index 0000000..5a1a43a --- /dev/null +++ b/src/test/rpc_tests.cpp @@ -0,0 +1,193 @@ +#include +#include + +#include "base58.h" +#include "util.h" +#include "rpcclient.h" +#include "rpcserver.h" +#include "wallet.h" + +using namespace std; +using namespace json_spirit; + +BOOST_AUTO_TEST_SUITE(rpc_tests) + +// Actually run some of the RPC tests over RPC +struct RPCServerFixture +{ + RPCServerFixture() { + SoftSetArg("-rpcuser", "diamondrpc"); + SoftSetArg("-rpcpassword", "dbrxGK62DXSJ6Cm3zgurPs3ML9uKxfwv5Z"); + StartRPCThreads(); + } + + ~RPCServerFixture() { + StopRPCThreads(); + } +}; + +// Read a response object and return false if there is an error found +bool readResponse(Object obj, int &error_code) { + // Initialize our error code as 0 because otherwise it may not have a value. + error_code = 0; + + const Value &error = find_value(obj, "error"); + + if (error.type() != null_type) + { + error_code = find_value(error.get_obj(), "code").get_int(); + return false; + } + + return true; +} + +Object callRPC(string strMethod, vector strParams) { + Array params = RPCConvertValues(strMethod, strParams); + return CallRPC(strMethod, params); +} + +BOOST_FIXTURE_TEST_CASE(rpc_scrapes, RPCServerFixture) +{ + // Wait a little bit to try to make sure the thread is fully started + MilliSleep(100); + + /* This is a valid private key and address, do NOT use this key in a real + * wallet, your coins will not be secure! */ + string strValidAddress = "ino6A7pZSa4Adsa6jLEzWvQfFW4WmLe2Ye"; + string strValidPrivKey = "PhWG55HKGLu6VVS27CyYrJ7q8z4xuFzqTGvj7ReRTn73ATTsCQgU"; + string strValidAddress2 = "iXDFQSLFFZ448bxXF9KPoJTSjqgCmQptRQ"; + + // error: {"code":-8,"message":"Staking address must be in wallet."} + string strMethod = "setscrapeaddress"; + vector strParams; + strParams.push_back(strValidAddress); + strParams.push_back(strValidAddress2); + + Object obj = callRPC(strMethod, strParams); + + int error_code; + BOOST_CHECK(!readResponse(obj, error_code)); + BOOST_CHECK_EQUAL(error_code, RPC_INVALID_PARAMETER); + + strMethod = "getscrapeaddress"; + strParams.clear(); + strParams.push_back(strValidAddress); + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(!readResponse(obj, error_code)); + BOOST_CHECK_EQUAL(error_code, RPC_INVALID_PARAMETER); + + strMethod = "deletescrapeaddress"; + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(!readResponse(obj, error_code)); + BOOST_CHECK_EQUAL(error_code, RPC_INVALID_PARAMETER); + + // Import a valid private key for testing on. + strMethod = "importprivkey"; + strParams.clear(); + strParams.push_back(strValidPrivKey); + strParams.push_back("test"); + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(readResponse(obj, error_code)); + + // // error: {"code":-5,"message":"Invalid address."} + strMethod = "setscrapeaddress"; + strParams.clear(); + strParams.push_back("ino6A7pZSa4A"); + strParams.push_back("iXDFQSLFFZ44"); + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(!readResponse(obj, error_code)); + BOOST_CHECK_EQUAL(error_code, RPC_INVALID_ADDRESS_OR_KEY); + + strMethod = "getscrapeaddress"; + strParams.clear(); + strParams.push_back("ino6A7pZSa4A"); + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(!readResponse(obj, error_code)); + BOOST_CHECK_EQUAL(error_code, RPC_INVALID_ADDRESS_OR_KEY); + + strMethod = "deletescrapeaddress"; + strParams.clear(); + strParams.push_back("ino6A7pZSa4A"); + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(!readResponse(obj, error_code)); + BOOST_CHECK_EQUAL(error_code, RPC_INVALID_ADDRESS_OR_KEY); + + // error: {"code":-5,"message":"Invalid scrape address."} + strMethod = "setscrapeaddress"; + strParams.clear(); + strParams.push_back(strValidAddress); + strParams.push_back("iXDFQSLFFZ44"); + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(!readResponse(obj, error_code)); + BOOST_CHECK_EQUAL(error_code, RPC_INVALID_ADDRESS_OR_KEY); + + // error: {"code":-8,"message":"Cannot set scrape address to the same as staking address."} + strParams.clear(); + strParams.push_back(strValidAddress); + strParams.push_back(strValidAddress); + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(!readResponse(obj, error_code)); + BOOST_CHECK_EQUAL(error_code, RPC_INVALID_PARAMETER); + + // error: ("code":-1,"message":"No scrape address set for address ") + strMethod = "getscrapeaddress"; + strParams.clear(); + strParams.push_back(strValidAddress); + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(!readResponse(obj, error_code)); + BOOST_CHECK_EQUAL(error_code, RPC_WALLET_ERROR); + + strMethod = "deletescrapeaddress"; + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(!readResponse(obj, error_code)); + BOOST_CHECK_EQUAL(error_code, RPC_WALLET_ERROR); + + // Valid setscrapeaddress + strMethod = "setscrapeaddress"; + strParams.clear(); + strParams.push_back(strValidAddress); + strParams.push_back(strValidAddress2); + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(readResponse(obj, error_code)); + + // Valid getscrapeaddress + strMethod = "getscrapeaddress"; + strParams.clear(); + strParams.push_back(strValidAddress); + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(readResponse(obj, error_code)); + + // Valid deletescrapeaddress + strMethod = "deletescrapeaddress"; + + obj = callRPC(strMethod, strParams); + + BOOST_CHECK(readResponse(obj, error_code)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet.cpp b/src/wallet.cpp index 633ca61..5f94e2d 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2016 Nathan Bass "IngCr3at1on" // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -167,14 +168,14 @@ bool CWallet::Lock() { if (IsLocked()) return true; - + if (fDebug) printf("Locking wallet.\n"); - + { LOCK(cs_wallet); CWalletDB wdb(strWalletFile); - + // -- load encrypted spend_secret of stealth addresses CStealthAddress sxAddrTemp; std::set::iterator it; @@ -186,7 +187,7 @@ bool CWallet::Lock() CStealthAddress &sxAddr = const_cast(*it); if (fDebug) printf("Recrypting stealth key %s\n", sxAddr.Encoded().c_str()); - + sxAddrTemp.scan_pubkey = sxAddr.scan_pubkey; if (!wdb.ReadStealthAddress(sxAddrTemp)) { @@ -426,23 +427,23 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) continue; // stealth address is not owned // -- CStealthAddress is only sorted on spend_pubkey CStealthAddress &sxAddr = const_cast(*it); - + if (fDebug) printf("Encrypting stealth key %s\n", sxAddr.Encoded().c_str()); - + std::vector vchCryptedSecret; - + CSecret vchSecret; vchSecret.resize(32); memcpy(&vchSecret[0], &sxAddr.spend_secret[0], 32); - + uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end()); if (!EncryptSecret(vMasterKey, vchSecret, iv, vchCryptedSecret)) { printf("Error: Failed encrypting stealth key %s\n", sxAddr.Encoded().c_str()); continue; }; - + sxAddr.spend_secret = vchCryptedSecret; pwalletdbEncryption->WriteStealthAddress(sxAddr); }; @@ -966,7 +967,7 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, int64_t& nReceived, nFee = allFee; } { - + BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64_t)& r, listReceived) { if (pwallet->mapAddressBook.count(r.first)) @@ -1285,7 +1286,7 @@ CAmount CWallet::GetAnonymizedBalance() const COutput out = COutput(pcoin, i, nDepth, mine); CTxIn vin = CTxIn(out.tx->GetHash(), out.i); - //if(IsSpent(out.tx->GetHash(), i) || !IsMine(pcoin->vout[i]) || !IsDenominated(vin)) continue; + //if(IsSpent(out.tx->GetHash(), i) || !IsMine(pcoin->vout[i]) || !IsDenominated(vin)) continue; if(pcoin->IsSpent(i) || !IsMine(pcoin->vout[i]) || !IsDenominated(vin)) continue; int rounds = GetInputDarksendRounds(vin); @@ -1467,7 +1468,7 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const continue; int nDepth = pcoin->GetDepthInMainChain(); - if (nDepth <= 0) // IONNOTE: coincontrol fix / ignore 0 confirm + if (nDepth <= 0) // IONNOTE: coincontrol fix / ignore 0 confirm continue; /* for (unsigned int i = 0; i < pcoin->vout.size(); i++) @@ -2548,28 +2549,28 @@ bool CWallet::CreateTransaction(CScript scriptPubKey, int64_t nValue, std::strin { vector< pair > vecSend; vecSend.push_back(make_pair(scriptPubKey, nValue)); - + if (sNarr.length() > 0) { std::vector vNarr(sNarr.c_str(), sNarr.c_str() + sNarr.length()); std::vector vNDesc; - + vNDesc.resize(2); vNDesc[0] = 'n'; vNDesc[1] = 'p'; - + CScript scriptN = CScript() << OP_RETURN << vNDesc << OP_RETURN << vNarr; - + vecSend.push_back(make_pair(scriptN, 0)); } - + // -- CreateTransaction won't place change between value and narr output. // narration output will be for preceding output - + int nChangePos; std::string strFailReason; bool rv = CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, nChangePos, strFailReason, coinControl); - + // -- narration will be added to mapValue later in FindStealthTransactions From CommitTransaction return rv; } @@ -2579,7 +2580,7 @@ bool CWallet::NewStealthAddress(std::string& sError, std::string& sLabel, CSteal { ec_secret scan_secret; ec_secret spend_secret; - + if (GenerateRandomSecret(scan_secret) != 0 || GenerateRandomSecret(spend_secret) != 0) { @@ -2587,7 +2588,7 @@ bool CWallet::NewStealthAddress(std::string& sError, std::string& sLabel, CSteal printf("Error CWallet::NewStealthAddress - %s\n", sError.c_str()); return false; }; - + ec_point scan_pubkey, spend_pubkey; if (SecretToPublicKey(scan_secret, scan_pubkey) != 0) { @@ -2595,14 +2596,14 @@ bool CWallet::NewStealthAddress(std::string& sError, std::string& sLabel, CSteal printf("Error CWallet::NewStealthAddress - %s\n", sError.c_str()); return false; }; - + if (SecretToPublicKey(spend_secret, spend_pubkey) != 0) { sError = "Could not get spend public key."; printf("Error CWallet::NewStealthAddress - %s\n", sError.c_str()); return false; }; - + if (fDebug) { printf("getnewstealthaddress: "); @@ -2610,37 +2611,37 @@ bool CWallet::NewStealthAddress(std::string& sError, std::string& sLabel, CSteal for (uint32_t i = 0; i < scan_pubkey.size(); ++i) printf("%02x", scan_pubkey[i]); printf("\n"); - + printf("spend_pubkey "); for (uint32_t i = 0; i < spend_pubkey.size(); ++i) printf("%02x", spend_pubkey[i]); printf("\n"); }; - - + + sxAddr.label = sLabel; sxAddr.scan_pubkey = scan_pubkey; sxAddr.spend_pubkey = spend_pubkey; - + sxAddr.scan_secret.resize(32); memcpy(&sxAddr.scan_secret[0], &scan_secret.e[0], 32); sxAddr.spend_secret.resize(32); memcpy(&sxAddr.spend_secret[0], &spend_secret.e[0], 32); - + return true; } bool CWallet::AddStealthAddress(CStealthAddress& sxAddr) { LOCK(cs_wallet); - + // must add before changing spend_secret stealthAddresses.insert(sxAddr); - + bool fOwned = sxAddr.scan_secret.size() == ec_secret_size; - - - + + + if (fOwned) { // -- owned addresses can only be added when wallet is unlocked @@ -2650,14 +2651,14 @@ bool CWallet::AddStealthAddress(CStealthAddress& sxAddr) stealthAddresses.erase(sxAddr); return false; }; - + if (IsCrypted()) { std::vector vchCryptedSecret; CSecret vchSecret; vchSecret.resize(32); memcpy(&vchSecret[0], &sxAddr.spend_secret[0], 32); - + uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end()); if (!EncryptSecret(vMasterKey, vchSecret, iv, vchCryptedSecret)) { @@ -2668,13 +2669,13 @@ bool CWallet::AddStealthAddress(CStealthAddress& sxAddr) sxAddr.spend_secret = vchCryptedSecret; }; }; - - + + bool rv = CWalletDB(strWalletFile).WriteStealthAddress(sxAddr); - + if (rv) NotifyAddressBookChanged(this, sxAddr, sxAddr.label, fOwned, CT_NEW); - + return rv; } @@ -2686,13 +2687,13 @@ bool CWallet::UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn) { if (it->scan_secret.size() < 32) continue; // stealth address is not owned - + // -- CStealthAddress are only sorted on spend_pubkey CStealthAddress &sxAddr = const_cast(*it); - + if (fDebug) printf("Decrypting stealth key %s\n", sxAddr.Encoded().c_str()); - + CSecret vchSecret; uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end()); if(!DecryptSecret(vMasterKeyIn, sxAddr.spend_secret, iv, vchSecret) @@ -2701,22 +2702,22 @@ bool CWallet::UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn) printf("Error: Failed decrypting stealth key %s\n", sxAddr.Encoded().c_str()); continue; }; - + ec_secret testSecret; memcpy(&testSecret.e[0], &vchSecret[0], 32); ec_point pkSpendTest; - + if (SecretToPublicKey(testSecret, pkSpendTest) != 0 || pkSpendTest != sxAddr.spend_pubkey) { printf("Error: Failed decrypting stealth key, public key mismatch %s\n", sxAddr.Encoded().c_str()); continue; }; - + sxAddr.spend_secret.resize(32); memcpy(&sxAddr.spend_secret[0], &vchSecret[0], 32); }; - + CryptedKeyMap::iterator mi = mapCryptedKeys.begin(); for (; mi != mapCryptedKeys.end(); ++mi) { @@ -2724,36 +2725,36 @@ bool CWallet::UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn) std::vector &vchCryptedSecret = (*mi).second.second; if (vchCryptedSecret.size() != 0) continue; - + CKeyID ckid = pubKey.GetID(); CBitcoinAddress addr(ckid); - + StealthKeyMetaMap::iterator mi = mapStealthKeyMeta.find(ckid); if (mi == mapStealthKeyMeta.end()) { printf("Error: No metadata found to add secret for %s\n", addr.ToString().c_str()); continue; }; - + CStealthKeyMetadata& sxKeyMeta = mi->second; - + CStealthAddress sxFind; sxFind.scan_pubkey = sxKeyMeta.pkScan.Raw(); - + std::set::iterator si = stealthAddresses.find(sxFind); if (si == stealthAddresses.end()) { printf("No stealth key found to add secret for %s\n", addr.ToString().c_str()); continue; }; - + if (fDebug) printf("Expanding secret for %s\n", addr.ToString().c_str()); - + ec_secret sSpendR; ec_secret sSpend; ec_secret sScan; - + if (si->spend_secret.size() != ec_secret_size || si->scan_secret.size() != ec_secret_size) { @@ -2762,27 +2763,27 @@ bool CWallet::UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn) } memcpy(&sScan.e[0], &si->scan_secret[0], ec_secret_size); memcpy(&sSpend.e[0], &si->spend_secret[0], ec_secret_size); - + ec_point pkEphem = sxKeyMeta.pkEphem.Raw(); if (StealthSecretSpend(sScan, pkEphem, sSpend, sSpendR) != 0) { printf("StealthSecretSpend() failed.\n"); continue; }; - + ec_point pkTestSpendR; if (SecretToPublicKey(sSpendR, pkTestSpendR) != 0) { printf("SecretToPublicKey() failed.\n"); continue; }; - + CSecret vchSecret; vchSecret.resize(ec_secret_size); - + memcpy(&vchSecret[0], &sSpendR.e[0], ec_secret_size); CKey ckey; - + try { ckey.Set(vchSecret.begin(), vchSecret.end(), true); //ckey.SetSecret(vchSecret, true); @@ -2790,40 +2791,40 @@ bool CWallet::UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn) printf("ckey.SetSecret() threw: %s.\n", e.what()); continue; }; - + CPubKey cpkT = ckey.GetPubKey(); - + if (!cpkT.IsValid()) { printf("cpkT is invalid.\n"); continue; }; - + if (cpkT != pubKey) { printf("Error: Generated secret does not match.\n"); continue; }; - + if (!ckey.IsValid()) { printf("Reconstructed key is invalid.\n"); continue; }; - + if (fDebug) { CKeyID keyID = cpkT.GetID(); CBitcoinAddress coinAddress(keyID); printf("Adding secret to key %s.\n", coinAddress.ToString().c_str()); }; - + if (!AddKey(ckey)) { printf("AddKey failed.\n"); continue; }; - + if (!CWalletDB(strWalletFile).EraseStealthKeyMeta(ckid)) printf("EraseStealthKeyMeta failed for %s\n", addr.ToString().c_str()); }; @@ -2834,16 +2835,16 @@ bool CWallet::UpdateStealthAddress(std::string &addr, std::string &label, bool a { if (fDebug) printf("UpdateStealthAddress %s\n", addr.c_str()); - - + + CStealthAddress sxAddr; - + if (!sxAddr.SetEncoded(addr)) return false; - + std::set::iterator it; it = stealthAddresses.find(sxAddr); - + ChangeType nMode = CT_UPDATED; CStealthAddress sxFound; if (it == stealthAddresses.end()) @@ -2862,33 +2863,33 @@ bool CWallet::UpdateStealthAddress(std::string &addr, std::string &label, bool a } else { sxFound = const_cast(*it); - + if (sxFound.label == label) { // no change return true; }; - + it->label = label; // update in .stealthAddresses - + if (sxFound.scan_secret.size() == ec_secret_size) { printf("UpdateStealthAddress: todo - update owned stealth address.\n"); return false; }; }; - + sxFound.label = label; - + if (!CWalletDB(strWalletFile).WriteStealthAddress(sxFound)) { printf("UpdateStealthAddress(%s) Write to db failed.\n", addr.c_str()); return false; }; - + bool fOwned = sxFound.scan_secret.size() == ec_secret_size; NotifyAddressBookChanged(this, sxFound, sxFound.label, fOwned, nMode); - + return true; } @@ -2896,20 +2897,20 @@ bool CWallet::CreateStealthTransaction(CScript scriptPubKey, int64_t nValue, std { vector< pair > vecSend; vecSend.push_back(make_pair(scriptPubKey, nValue)); - + CScript scriptP = CScript() << OP_RETURN << P; if (narr.size() > 0) scriptP = scriptP << OP_RETURN << narr; - + vecSend.push_back(make_pair(scriptP, 1)); - + // -- shuffle inputs, change output won't mix enough as it must be not fully random for plantext narrations std::random_shuffle(vecSend.begin(), vecSend.end()); - + int nChangePos; std::string strFailReason; bool rv = CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, nChangePos, strFailReason, coinControl); - + // -- the change txn is inserted in a random pos, check here to match narr to output if (rv && narr.size() > 0) { @@ -2918,7 +2919,7 @@ bool CWallet::CreateStealthTransaction(CScript scriptPubKey, int64_t nValue, std if (wtxNew.vout[k].scriptPubKey != scriptPubKey || wtxNew.vout[k].nValue != nValue) continue; - + char key[64]; if (snprintf(key, sizeof(key), "n_%u", k) < 1) { @@ -2929,7 +2930,7 @@ bool CWallet::CreateStealthTransaction(CScript scriptPubKey, int64_t nValue, std break; }; }; - + return rv; } @@ -2983,76 +2984,76 @@ bool CWallet::SendStealthMoneyToDestination(CStealthAddress& sxAddress, int64_t sError = "Insufficient funds"; return false; }; - - + + ec_secret ephem_secret; ec_secret secretShared; ec_point pkSendTo; ec_point ephem_pubkey; - + if (GenerateRandomSecret(ephem_secret) != 0) { sError = "GenerateRandomSecret failed."; return false; }; - + if (StealthSecret(ephem_secret, sxAddress.scan_pubkey, sxAddress.spend_pubkey, secretShared, pkSendTo) != 0) { sError = "Could not generate receiving public key."; return false; }; - + CPubKey cpkTo(pkSendTo); if (!cpkTo.IsValid()) { sError = "Invalid public key generated."; return false; }; - + CKeyID ckidTo = cpkTo.GetID(); - + CBitcoinAddress addrTo(ckidTo); - + if (SecretToPublicKey(ephem_secret, ephem_pubkey) != 0) { sError = "Could not generate ephem public key."; return false; }; - + if (fDebug) { printf("Stealth send to generated pubkey %" PRIszu ": %s\n", pkSendTo.size(), HexStr(pkSendTo).c_str()); printf("hash %s\n", addrTo.ToString().c_str()); printf("ephem_pubkey %" PRIszu ": %s\n", ephem_pubkey.size(), HexStr(ephem_pubkey).c_str()); }; - + std::vector vchNarr; if (sNarr.length() > 0) { SecMsgCrypter crypter; crypter.SetKey(&secretShared.e[0], &ephem_pubkey[0]); - + if (!crypter.Encrypt((uint8_t*)&sNarr[0], sNarr.length(), vchNarr)) { sError = "Narration encryption failed."; return false; }; - + if (vchNarr.size() > 48) { sError = "Encrypted narration is too long."; return false; }; }; - + // -- Parse Bitcoin address CScript scriptPubKey; scriptPubKey.SetDestination(addrTo.Get()); - + if ((sError = SendStealthMoney(scriptPubKey, nValue, ephem_pubkey, vchNarr, sNarr, wtxNew, fAskFee)) != "") return false; - - + + return true; } @@ -3060,32 +3061,32 @@ bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNar { if (fDebug) LogPrintf("FindStealthTransactions() tx: %s\n", tx.GetHash().GetHex().c_str()); - + mapNarr.clear(); - + LOCK(cs_wallet); ec_secret sSpendR; ec_secret sSpend; ec_secret sScan; ec_secret sShared; - + ec_point pkExtracted; - + std::vector vchEphemPK; std::vector vchDataB; std::vector vchENarr; opcodetype opCode; char cbuf[256]; - + int32_t nOutputIdOuter = -1; BOOST_FOREACH(const CTxOut& txout, tx.vout) { nOutputIdOuter++; // -- for each OP_RETURN need to check all other valid outputs - + //printf("txout scriptPubKey %s\n", txout.scriptPubKey.ToString().c_str()); CScript::const_iterator itTxA = txout.scriptPubKey.begin(); - + if (!txout.scriptPubKey.GetOp(itTxA, opCode, vchEphemPK) || opCode != OP_RETURN) continue; @@ -3104,7 +3105,7 @@ bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNar && vchENarr.size() > 0) { std::string sNarr = std::string(vchENarr.begin(), vchENarr.end()); - + snprintf(cbuf, sizeof(cbuf), "n_%d", nOutputIdOuter-1); // plaintext narration always matches preceding value output mapNarr[cbuf] = sNarr; } else @@ -3112,67 +3113,67 @@ bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNar printf("Warning: FindStealthTransactions() tx: %s, Could not extract plaintext narration.\n", tx.GetHash().GetHex().c_str()); }; } - + continue; } - + int32_t nOutputId = -1; nStealth++; BOOST_FOREACH(const CTxOut& txoutB, tx.vout) { nOutputId++; - + if (&txoutB == &txout) continue; - + bool txnMatch = false; // only 1 txn will match an ephem pk //printf("txoutB scriptPubKey %s\n", txoutB.scriptPubKey.ToString().c_str()); - + CTxDestination address; if (!ExtractDestination(txoutB.scriptPubKey, address)) continue; - + if (address.type() != typeid(CKeyID)) continue; - + CKeyID ckidMatch = boost::get(address); - + if (HaveKey(ckidMatch)) // no point checking if already have key continue; - + std::set::iterator it; for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it) { if (it->scan_secret.size() != ec_secret_size) continue; // stealth address is not owned - + //printf("it->Encodeded() %s\n", it->Encoded().c_str()); memcpy(&sScan.e[0], &it->scan_secret[0], ec_secret_size); - + if (StealthSecret(sScan, vchEphemPK, it->spend_pubkey, sShared, pkExtracted) != 0) { printf("StealthSecret failed.\n"); continue; }; //printf("pkExtracted %" PRIszu ": %s\n", pkExtracted.size(), HexStr(pkExtracted).c_str()); - + CPubKey cpkE(pkExtracted); - + if (!cpkE.IsValid()) continue; CKeyID ckidE = cpkE.GetID(); - + if (ckidMatch != ckidE) continue; - + if (fDebug) printf("Found stealth txn to address %s\n", it->Encoded().c_str()); - + if (IsLocked()) { if (fDebug) printf("Wallet is locked, adding key without secret.\n"); - + // -- add key without secret std::vector vchEmpty; AddCryptedKey(cpkE, vchEmpty); @@ -3180,14 +3181,14 @@ bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNar CBitcoinAddress coinAddress(keyId); std::string sLabel = it->Encoded(); SetAddressBookName(keyId, sLabel); - + CPubKey cpkEphem(vchEphemPK); CPubKey cpkScan(it->scan_pubkey); CStealthKeyMetadata lockedSkMeta(cpkEphem, cpkScan); - + if (!CWalletDB(strWalletFile).WriteStealthKeyMeta(keyId, lockedSkMeta)) printf("WriteStealthKeyMeta failed for %s\n", coinAddress.ToString().c_str()); - + mapStealthKeyMeta[keyId] = lockedSkMeta; nFoundStealth++; } else @@ -3195,27 +3196,27 @@ bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNar if (it->spend_secret.size() != ec_secret_size) continue; memcpy(&sSpend.e[0], &it->spend_secret[0], ec_secret_size); - - + + if (StealthSharedToSecretSpend(sShared, sSpend, sSpendR) != 0) { printf("StealthSharedToSecretSpend() failed.\n"); continue; }; - + ec_point pkTestSpendR; if (SecretToPublicKey(sSpendR, pkTestSpendR) != 0) { printf("SecretToPublicKey() failed.\n"); continue; }; - + CSecret vchSecret; vchSecret.resize(ec_secret_size); - + memcpy(&vchSecret[0], &sSpendR.e[0], ec_secret_size); CKey ckey; - + try { ckey.Set(vchSecret.begin(), vchSecret.end(), true); //ckey.SetSecret(vchSecret, true); @@ -3223,38 +3224,38 @@ bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNar printf("ckey.SetSecret() threw: %s.\n", e.what()); continue; }; - + CPubKey cpkT = ckey.GetPubKey(); if (!cpkT.IsValid()) { printf("cpkT is invalid.\n"); continue; }; - + if (!ckey.IsValid()) { printf("Reconstructed key is invalid.\n"); continue; }; - + CKeyID keyID = cpkT.GetID(); if (fDebug) { CBitcoinAddress coinAddress(keyID); printf("Adding key %s.\n", coinAddress.ToString().c_str()); }; - + if (!AddKey(ckey)) { printf("AddKey failed.\n"); continue; }; - + std::string sLabel = it->Encoded(); SetAddressBookName(keyID, sLabel); nFoundStealth++; }; - + if (txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr) && opCode == OP_RETURN && txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr) @@ -3269,11 +3270,11 @@ bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNar continue; }; std::string sNarr = std::string(vchNarr.begin(), vchNarr.end()); - + snprintf(cbuf, sizeof(cbuf), "n_%d", nOutputId); mapNarr[cbuf] = sNarr; }; - + txnMatch = true; break; }; @@ -3281,7 +3282,7 @@ bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNar break; }; }; - + return true; }; @@ -3376,7 +3377,7 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int for (unsigned int n=0; nGetHash(), pcoin.second); int64_t nBlockTime; @@ -3493,9 +3494,28 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int if (nReward <= 0) return false; - nCredit += nReward; + /* Check the staking address against the scrape addresses in the + * walletdb and see if it has a scrape address for it, if it does + * send the reward to the scrape address. */ + CTxDestination address; + ExtractDestination(txNew.vout[1].scriptPubKey, address); + CBitcoinAddress addr(address); + + string strScrapeAddress; + if (HasScrapeAddress(addr.ToString()) && ReadScrapeAddress(addr.ToString(), strScrapeAddress)) { + CScript stakescript; + CBitcoinAddress scrapeaddr(strScrapeAddress); + CTxDestination scrape = scrapeaddr.Get(); + if (fDebug && GetBoolArg("-printcoinstake", false)) + strprintf("CreateCoinStake : a scrape address has been set for %s to %s, sending reward there.\n", addr.ToString().c_str(), scrapeaddr.ToString().c_str()); + + stakescript.SetDestination(scrape); + txNew.vout.push_back(CTxOut(nReward, stakescript)); + } else { + nCredit += nReward; + } } - + if (nCredit >= GetStakeSplitThreshold()) txNew.vout.push_back(CTxOut(0, txNew.vout[1].scriptPubKey)); //split stake @@ -3592,7 +3612,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) { mapValue_t mapNarr; FindStealthTransactions(wtxNew, mapNarr); - + if (!mapNarr.empty()) { BOOST_FOREACH(const PAIRTYPE(string,string)& item, mapNarr) diff --git a/src/wallet.h b/src/wallet.h index bc02b26..b0fb7e1 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2016 Nathan Bass "IngCr3at1on" // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_H @@ -45,8 +46,9 @@ enum WalletFeature FEATURE_WALLETCRYPT = 40000, // wallet encryption FEATURE_COMPRPUBKEY = 60000, // compressed public keys + FEATURE_SCRAPEADDRESS = 60001, // scrape addresses for staking wallets - FEATURE_LATEST = 60000 + FEATURE_LATEST = 60001 }; enum AvailableCoinsType @@ -140,9 +142,9 @@ class CWallet : public CCryptoKeyStore, public CWalletInterface std::set stealthAddresses; StealthKeyMetaMap mapStealthKeyMeta; - + int nLastFilteredHeight; - + uint32_t nStealth, nFoundStealth; // for reporting, zero before use @@ -164,7 +166,7 @@ class CWallet : public CCryptoKeyStore, public CWalletInterface strWalletFile = strWalletFileIn; fFileBacked = true; } - + void SetNull() { nWalletVersion = FEATURE_BASE; @@ -283,7 +285,7 @@ class CWallet : public CCryptoKeyStore, public CWalletInterface bool AddStealthAddress(CStealthAddress& sxAddr); bool UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn); bool UpdateStealthAddress(std::string &addr, std::string &label, bool addIfNotExist); - + bool CreateStealthTransaction(CScript scriptPubKey, int64_t nValue, std::vector& P, std::vector& narr, std::string& sNarr, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl=NULL); std::string SendStealthMoney(CScript scriptPubKey, int64_t nValue, std::vector& P, std::vector& narr, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee=false); bool SendStealthMoneyToDestination(CStealthAddress& sxAddress, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, std::string& sError, bool fAskFee=false); @@ -439,6 +441,30 @@ class CWallet : public CCryptoKeyStore, public CWalletInterface * @note called with lock cs_wallet held. */ boost::signals2::signal NotifyTransactionChanged; + + bool WriteScrapeAddress(const std::string strAddress, const std::string strScrapeAddress) + { + LOCK(cs_wallet); + return CWalletDB(strWalletFile).WriteScrapeAddress(strAddress, strScrapeAddress); + } + + bool EraseScrapeAddress(const std::string strAddress) + { + LOCK(cs_wallet); + return CWalletDB(strWalletFile).EraseScrapeAddress(strAddress); + } + + bool ReadScrapeAddress(const std::string strAddress, std::string &strScrapeAddress) + { + LOCK(cs_wallet); + return CWalletDB(strWalletFile).ReadScrapeAddress(strAddress, strScrapeAddress); + } + + bool HasScrapeAddress(const std::string strAddress) + { + LOCK(cs_wallet); + return CWalletDB(strWalletFile).HasScrapeAddress(strAddress); + } }; /** A key allocated from the key pool. */ diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 5097ec0..dfc794c 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2016 Nathan Bass "IngCr3at1on" // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -16,6 +17,7 @@ using namespace std; using namespace boost; +using namespace json_spirit; static uint64_t nAccountingEntryNumber = 0; @@ -424,12 +426,12 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { if (fDebug) printf("WalletDB ReadKeyValue sxAddr\n"); - + CStealthAddress sxAddr; ssValue >> sxAddr; - + pwallet->stealthAddresses.insert(sxAddr); - } + } else if (strType == "acentry") { string strAccount; @@ -559,7 +561,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { if (fDebug) printf("WalletDB ReadKeyValue sxKeyMeta\n"); - + CKeyID keyId; ssKey >> keyId; CStealthKeyMetadata sxKeyMeta; @@ -630,6 +632,65 @@ static bool IsKeyType(string strType) strType == "mkey" || strType == "ckey"); } +bool CWalletDB::WriteScrapeAddress(const string strAddress, const string strScrapeAddress) +{ + nWalletDBUpdated++; + return Write(make_pair(string("scrapeaddress"), strAddress), strScrapeAddress); +} + +bool CWalletDB::EraseScrapeAddress(const string strAddress) +{ + nWalletDBUpdated++; + return Erase(make_pair(string("scrapeaddress"), strAddress)); +} + +bool CWalletDB::ReadScrapeAddress(const string strAddress, string &strScrapeAddress) +{ + return Read(make_pair(string("scrapeaddress"), strAddress), strScrapeAddress); +} + +bool CWalletDB::HasScrapeAddress(const string strAddress) +{ + return Exists(make_pair(string("scrapeaddress"), strAddress)); +} + +bool CWalletDB::DumpScrapeAddresses(Object &ScrapeAddresses) +{ + Dbc* pcursor = GetCursor(); + if (!pcursor) + throw runtime_error("DumpScrapeAddresses() : cannot create DB cursor"); + unsigned int fFlags = DB_SET_RANGE; + + for (;;) { + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + if (fFlags == DB_SET_RANGE) + ssKey << make_pair(std::string("scrapeaddress"), string("")); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); + fFlags = DB_NEXT; + if (ret == DB_NOTFOUND) + break; + + else if (ret != 0) { + pcursor->close(); + throw runtime_error("DumpScrapeAddresses() : error scanning DB"); + } + + // Unserialize + string strType, address, scrape_address; + ssKey >> strType; + if (strType != "scrapeaddress") + break; + + ssKey >> address; + ssValue >> scrape_address; + ScrapeAddresses.push_back(Pair(address, scrape_address)); + } + + pcursor->close(); + return true; +} + DBErrors CWalletDB::LoadWallet(CWallet* pwallet) { pwallet->vchDefaultKey = CPubKey(); diff --git a/src/walletdb.h b/src/walletdb.h index 4d80f91..fb5cd1a 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -1,5 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2016 Nathan Bass "IngCr3at1on" // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLETDB_H @@ -7,6 +8,7 @@ #include "db.h" #include "key.h" +#include "json/json_spirit_value.h" #include "stealth.h" #include @@ -72,13 +74,13 @@ class CStealthKeyMetadata // -- used to get secret for keys created by stealth transaction with wallet locked public: CStealthKeyMetadata() {}; - + CStealthKeyMetadata(CPubKey pkEphem_, CPubKey pkScan_) { pkEphem = pkEphem_; pkScan = pkScan_; }; - + CPubKey pkEphem; CPubKey pkScan; @@ -134,7 +136,7 @@ class CWalletDB : public CDB bool WriteStealthKeyMeta(const CKeyID& keyId, const CStealthKeyMetadata& sxKeyMeta); bool EraseStealthKeyMeta(const CKeyID& keyId); - bool WriteStealthAddress(const CStealthAddress& sxAddr); + bool WriteStealthAddress(const CStealthAddress& sxAddr); bool ReadStealthAddress(CStealthAddress& sxAddr); bool WriteionNodeConfig(std::string sAlias, const CionNodeConfig& nodeConfig); @@ -173,6 +175,12 @@ class CWalletDB : public CDB DBErrors LoadWallet(CWallet* pwallet); static bool Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys); static bool Recover(CDBEnv& dbenv, std::string filename); + + bool WriteScrapeAddress(const std::string strAddress, const std::string strScrapeAddress); + bool EraseScrapeAddress(const std::string strAddress); + bool ReadScrapeAddress(const std::string strAddress, std::string &strScrapeAddress); + bool DumpScrapeAddresses(json_spirit::Object &ScrapeAddresses); + bool HasScrapeAddress(const std::string strAddress); }; bool BackupWallet(const CWallet& wallet, const std::string& strDest); From aa7e586d0d84a204ec226ffd4ad53c3d8b957487 Mon Sep 17 00:00:00 2001 From: Nathan Bass Date: Sun, 10 Jul 2016 13:28:58 -0500 Subject: [PATCH 4/4] Add qt functionality for scrape addresses. --- src/qt/addressbookpage.cpp | 14 ++- src/qt/addresstablemodel.cpp | 161 +++++++++++++++++++++---------- src/qt/addresstablemodel.h | 3 +- src/qt/transactionrecord.cpp | 48 ++++++++- src/qt/transactionrecord.h | 6 +- src/qt/transactiontablemodel.cpp | 95 ++++++++++++------ 6 files changed, 239 insertions(+), 88 deletions(-) diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 6570e90..009fbaf 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -121,14 +121,26 @@ void AddressBookPage::setModel(AddressTableModel *model) // Receive filter proxyModel->setFilterRole(AddressTableModel::TypeRole); proxyModel->setFilterFixedString(AddressTableModel::Receive); + + // Set this slightly earlier so that we can adjust our columns by tab. + ui->tableView->setModel(proxyModel); + // Only display the scrape address for receiving tab (cheap hack). + ui->tableView->horizontalHeader()->resizeSection( + AddressTableModel::ScrapeAddress, 320); break; case SendingTab: // Send filter proxyModel->setFilterRole(AddressTableModel::TypeRole); proxyModel->setFilterFixedString(AddressTableModel::Send); + + // Set this slightly earlier so that we can adjust our columns by tab. + ui->tableView->setModel(proxyModel); + // Do not display the scrape address for the send tab (cheap hack). + ui->tableView->horizontalHeader()->resizeSection( + AddressTableModel::ScrapeAddress, 0); + break; } - ui->tableView->setModel(proxyModel); ui->tableView->sortByColumn(0, Qt::AscendingOrder); // Set column widths diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index b70acfe..8585a94 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -22,9 +22,12 @@ struct AddressTableEntry Type type; QString label; QString address; + QString scrape_address; bool stealth; AddressTableEntry() {} + AddressTableEntry(Type type, const QString &label, const QString &address, const QString &scrape_address, const bool &stealth = false): + type(type), label(label), address(address), scrape_address(scrape_address), stealth(stealth) {} AddressTableEntry(Type type, const QString &label, const QString &address, const bool &stealth = false): type(type), label(label), address(address), stealth(stealth) {} }; @@ -66,9 +69,21 @@ class AddressTablePriv const CBitcoinAddress& address = item.first; const std::string& strName = item.second; bool fMine = IsMine(*wallet, address.Get()); - cachedAddressTable.append(AddressTableEntry(fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending, - QString::fromStdString(strName), - QString::fromStdString(address.ToString()))); + + if (fMine) { + std::string addr; + if (wallet->HasScrapeAddress(address.ToString())) + wallet->ReadScrapeAddress(address.ToString(), addr); + + cachedAddressTable.append(AddressTableEntry(AddressTableEntry::Receiving, + QString::fromStdString(strName), + QString::fromStdString(address.ToString()), + QString::fromStdString(addr))); + } else { + cachedAddressTable.append(AddressTableEntry(AddressTableEntry::Sending, + QString::fromStdString(strName), + QString::fromStdString(address.ToString()))); + } } std::set::iterator it; @@ -95,6 +110,7 @@ class AddressTablePriv int lowerIndex = (lower - cachedAddressTable.begin()); int upperIndex = (upper - cachedAddressTable.begin()); bool inModel = (lower != upper); + AddressTableEntry::Type newEntryType = isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending; switch(status) @@ -153,7 +169,7 @@ class AddressTablePriv AddressTableModel::AddressTableModel(CWallet *wallet, WalletModel *parent) : QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0) { - columns << tr("Label") << tr("Address"); + columns << tr("Label") << tr("Address") << tr("Scrape Address"); priv = new AddressTablePriv(wallet, this); priv->refreshAddressTable(); } @@ -197,6 +213,17 @@ QVariant AddressTableModel::data(const QModelIndex &index, int role) const } case Address: return rec->address; + + case ScrapeAddress: + // No scrape address for sending tab. + if (rec->type == AddressTableEntry::Sending) + break; + + if (rec->scrape_address.isEmpty() && role == Qt::DisplayRole) { + return tr("(no scrape address)"); + } else { + return rec->scrape_address; + } } } else if (role == Qt::FontRole) @@ -253,43 +280,74 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, } break; case Address: - std::string sTemp = value.toString().toStdString(); - if (IsStealthAddress(sTemp)) - { - printf("TODO\n"); - editStatus = INVALID_ADDRESS; - return false; - } - // Do nothing, if old address == new address - if(CBitcoinAddress(rec->address.toStdString()) == CBitcoinAddress(value.toString().toStdString())) - { - editStatus = NO_CHANGES; - return false; - } - // Refuse to set invalid address, set error status and return false - else if(!walletModel->validateAddress(value.toString())) - { - editStatus = INVALID_ADDRESS; - return false; - } - // Check for duplicate addresses to prevent accidental deletion of addresses, if you try - // to paste an existing address over another address (with a different label) - else if(wallet->mapAddressBook.count(CBitcoinAddress(value.toString().toStdString()).Get())) - { - editStatus = DUPLICATE_ADDRESS; - return false; - } - // Double-check that we're not overwriting a receiving address - else if(rec->type == AddressTableEntry::Sending) { + std::string sTemp = value.toString().toStdString(); + if (IsStealthAddress(sTemp)) + { + printf("TODO\n"); + editStatus = INVALID_ADDRESS; + return false; + } + // Do nothing, if old address == new address + if(CBitcoinAddress(rec->address.toStdString()) == CBitcoinAddress(value.toString().toStdString())) + { + editStatus = NO_CHANGES; + return false; + } + // Refuse to set invalid address, set error status and return false + else if(!walletModel->validateAddress(value.toString())) + { + editStatus = INVALID_ADDRESS; + return false; + } + // Check for duplicate addresses to prevent accidental deletion of addresses, if you try + // to paste an existing address over another address (with a different label) + else if(wallet->mapAddressBook.count(CBitcoinAddress(value.toString().toStdString()).Get())) { - LOCK(wallet->cs_wallet); - // Remove old entry - wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get()); - // Add new entry with new address - wallet->SetAddressBookName(CBitcoinAddress(value.toString().toStdString()).Get(), rec->label.toStdString()); + editStatus = DUPLICATE_ADDRESS; + return false; } + // Double-check that we're not overwriting a receiving address + else if(rec->type == AddressTableEntry::Sending) + { + { + LOCK(wallet->cs_wallet); + // Remove old entry + wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()).Get()); + // Add new entry with new address + wallet->SetAddressBookName(CBitcoinAddress(value.toString().toStdString()).Get(), rec->label.toStdString()); + } + } + } + break; + case ScrapeAddress: + // No scrape address for sending tab. + if (rec->type == AddressTableEntry::Sending) + break; + + /* If passing an empty string delete the current scrape address if + * one exists. */ + if (value.toString().toStdString().empty()) { + if (wallet->HasScrapeAddress(rec->address.toStdString())) + if (!wallet->EraseScrapeAddress(rec->address.toStdString())) + return false; + } else { + // Confirm the scrape address is a valid address before setting it + if (!walletModel->validateAddress(value.toString())) { + editStatus = INVALID_ADDRESS; + return false; + } + + /* Confirm we are not setting the scrape address to the same + * address as the staking address. */ + if (rec->address == value.toString()) + return false; + + if (!wallet->WriteScrapeAddress(rec->address.toStdString(), value.toString().toStdString())) + return false; } + + rec->scrape_address = value.toString(); break; } return true; @@ -317,9 +375,10 @@ Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; // Can edit address and label for sending addresses, - // and only label for receiving addresses. + // label and scrapeadddress for receiving addresses. if(rec->type == AddressTableEntry::Sending || - (rec->type == AddressTableEntry::Receiving && index.column()==Label)) + (rec->type == AddressTableEntry::Receiving && index.column()==Label) || + (rec->type == AddressTableEntry::Receiving && index.column()==ScrapeAddress)) { retval |= Qt::ItemIsEditable; } @@ -363,21 +422,21 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con editStatus = INVALID_ADDRESS; return QString(); } - + // -- Check for duplicate addresses { LOCK(wallet->cs_wallet); - + if (wallet->stealthAddresses.count(sxAddr)) { editStatus = DUPLICATE_ADDRESS; return QString(); }; - + sxAddr.label = strLabel; wallet->AddStealthAddress(sxAddr); } - + } else { if (!walletModel->validateAddress(address)) @@ -393,7 +452,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con editStatus = DUPLICATE_ADDRESS; return QString(); }; - + wallet->SetAddressBookName(CBitcoinAddress(strAddress).Get(), strLabel); } } @@ -402,14 +461,14 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con { // Generate a new address to associate with given label WalletModel::UnlockContext ctx(walletModel->requestUnlock()); - + if(!ctx.isValid()) { // Unlock wallet failed or was cancelled editStatus = WALLET_UNLOCK_FAILURE; return QString(); } - + if (addressType == AT_Stealth) { CStealthAddress newStealthAddr; @@ -430,7 +489,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con return QString(); } strAddress = CBitcoinAddress(newKey.GetID()).ToString(); - + { LOCK(wallet->cs_wallet); wallet->SetAddressBookName(CBitcoinAddress(strAddress).Get(), strLabel); @@ -442,7 +501,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con return QString(); } - + return QString::fromStdString(strAddress); } @@ -470,18 +529,18 @@ QString AddressTableModel::labelForAddress(const QString &address) const { LOCK(wallet->cs_wallet); std::string sAddr = address.toStdString(); - + if (sAddr.length() > 75) { CStealthAddress sxAddr; if (!sxAddr.SetEncoded(sAddr)) return QString(); - + std::set::iterator it; it = wallet->stealthAddresses.find(sxAddr); if (it == wallet->stealthAddresses.end()) return QString(); - + return QString::fromStdString(it->label); } else { diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 97537a2..ce6fd44 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -28,7 +28,8 @@ class AddressTableModel : public QAbstractTableModel enum ColumnIndex { Label = 0, /**< User specified label */ Address = 1, /**< Bitcoin address */ - Type = 2 /**< Address type */ + ScrapeAddress = 2, /**< Ion scrape address (receiving tab only) */ + Type = 3 /**< Address type */ }; enum RoleIndex { diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index e5b2c9a..a6ff661 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -32,7 +32,47 @@ QList TransactionRecord::decomposeTransaction(const CWallet * uint256 hash = wtx.GetHash(), hashPrev = 0; std::map mapValue = wtx.mapValue; - if (nNet > 0 || wtx.IsCoinBase() || wtx.IsCoinStake()) + if (wtx.IsCoinStake()) + { + // Stake generation + TransactionRecord sub(hash, nTime, TransactionRecord::StakeMint, "", -nDebit, wtx.GetValueOut()); + CTxDestination stakingAddress, rewardAddress; + /* vout[0] is blank, just marks the transaction as stake + * vout[1] is the first stake output and therefore always related to + * the staking address. */ + if (ExtractDestination(wtx.vout[1].scriptPubKey, stakingAddress)) { + if (ExtractDestination(wtx.vout[wtx.vout.size() - 1].scriptPubKey, rewardAddress)) { + /* If the staking address isn't in the wallet than this is an + * external scrape received from another wallet. */ + if (!IsMine(*wallet, stakingAddress)) { + sub.type = TransactionRecord::ExternalScrape; + // In this instance the reward is always in the last output. + sub.credit = wtx.vout[wtx.vout.size() - 1].nValue; + sub.address = CBitcoinAddress(rewardAddress).ToString(); + /* The reward address is not in the wallet but the address is + * so the reward went to a scrape, treat it like a normal mint + * but display the scrape address. */ + } else if (!IsMine(*wallet, rewardAddress)) { + sub.type = TransactionRecord::ScrapeToExternal; + sub.address = CBitcoinAddress(rewardAddress).ToString(); + /* The address is in the wallet but it's different than the staking + * address, display the reward address and the stake amount. */ + } else if (CBitcoinAddress(stakingAddress).ToString() != CBitcoinAddress(rewardAddress).ToString()) { + sub.type = TransactionRecord::LocalScrape; + sub.address = CBitcoinAddress(rewardAddress).ToString(); + // The reweard went to the same address as the staking address (not a scrape) + } else { + sub.address = CBitcoinAddress(stakingAddress).ToString(); + } + // No destination in the last output (this should not happen) + } else { + sub.address = CBitcoinAddress(stakingAddress).ToString(); + } + } + + parts.append(sub); + } + else if (nNet > 0 || wtx.IsCoinBase()) { // // Credit @@ -62,6 +102,7 @@ QList TransactionRecord::decomposeTransaction(const CWallet * // Generated (proof-of-work) sub.type = TransactionRecord::Generated; } + /* if (wtx.IsCoinStake()) { // Generated (proof-of-stake) @@ -73,7 +114,7 @@ QList TransactionRecord::decomposeTransaction(const CWallet * sub.credit = nNet > 0 ? nNet : wtx.GetValueOut() - nDebit; hashPrev = hash; } - + */ parts.append(sub); } } @@ -190,7 +231,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx) } // For generated transactions, determine maturity - else if(type == TransactionRecord::Generated) + else if(type == TransactionRecord::Generated || type == TransactionRecord::StakeMint || type == TransactionRecord::ExternalScrape || type == TransactionRecord::LocalScrape || type == TransactionRecord::ScrapeToExternal) { if (wtx.GetBlocksToMaturity() > 0) { @@ -254,4 +295,3 @@ QString TransactionRecord::formatSubTxId(const uint256 &hash, int vout) { return QString::fromStdString(hash.ToString() + strprintf("-%03d", vout)); } - diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index bc7403a..c7f4d43 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -71,7 +71,11 @@ class TransactionRecord SendToOther, RecvWithAddress, RecvFromOther, - SendToSelf + SendToSelf, + StakeMint, + ExternalScrape, + LocalScrape, + ScrapeToExternal }; /** Number of confirmation recommended for accepting a transaction */ diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 1b56889..7ffa048 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -292,13 +292,15 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons status = tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx->status.depth).arg(TransactionRecord::RecommendedNumConfirmations); break; case TransactionStatus::Confirmed: - status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth); + wtx->type == TransactionRecord::ScrapeToExternal ? status = tr("Minted balance is available at the reward address not found in this wallet.") : + status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth); break; case TransactionStatus::Conflicted: status = tr("Conflicted"); break; case TransactionStatus::Immature: - status = tr("Immature (%1 confirmations, will be available after %2)").arg(wtx->status.depth).arg(wtx->status.depth + wtx->status.matures_in); + wtx->type == TransactionRecord::ScrapeToExternal ? status = tr("Minted balance will be available at reward address in %n more blocks").arg(wtx->status.matures_in) : + status = tr("Immature (%1 confirmations, will be available after %2)").arg(wtx->status.depth).arg(wtx->status.depth + wtx->status.matures_in); break; case TransactionStatus::MaturesWarning: status = tr("This block was not received by any other nodes and will probably not be accepted!"); @@ -356,6 +358,12 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const return tr("Payment to yourself"); case TransactionRecord::Generated: return tr("Mined"); + case TransactionRecord::ExternalScrape: + return tr("External scrape"); + case TransactionRecord::LocalScrape: + return tr("Local scrape"); + case TransactionRecord::ScrapeToExternal: + return tr("Scraped to external"); default: return QString(); } @@ -388,6 +396,9 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, b case TransactionRecord::RecvWithAddress: case TransactionRecord::SendToAddress: case TransactionRecord::Generated: + case TransactionRecord::ExternalScrape: + case TransactionRecord::LocalScrape: + case TransactionRecord::ScrapeToExternal: return lookupAddress(wtx->address, tooltip); case TransactionRecord::SendToOther: return QString::fromStdString(wtx->address); @@ -405,6 +416,9 @@ QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const case TransactionRecord::RecvWithAddress: case TransactionRecord::SendToAddress: case TransactionRecord::Generated: + case TransactionRecord::ExternalScrape: + case TransactionRecord::LocalScrape: + case TransactionRecord::ScrapeToExternal: { QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address)); if(label.isEmpty()) @@ -423,7 +437,9 @@ QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit); if(showUnconfirmed) { - if(!wtx->status.countsForBalance) + /* Always display ScrapeToExternal transactions as if the coins are + * immature because they are not and will never be in this wallet. */ + if(!wtx->status.countsForBalance || !wtx->status.Confirmed || wtx->status.status != TransactionStatus::Confirmed || wtx->type == TransactionRecord::ScrapeToExternal) { str = QString("[") + str + QString("]"); } @@ -433,36 +449,55 @@ QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const { - switch(wtx->status.status) + if(wtx->type == TransactionRecord::Generated || wtx->type == TransactionRecord::StakeMint || wtx->type == TransactionRecord::ExternalScrape || wtx->type == TransactionRecord::LocalScrape) { - case TransactionStatus::OpenUntilBlock: - case TransactionStatus::OpenUntilDate: - return QColor(64,64,255); - case TransactionStatus::Offline: - return QColor(192,192,192); - case TransactionStatus::Unconfirmed: - return QIcon(":/icons/transaction_0"); - case TransactionStatus::Confirming: - switch(wtx->status.depth) + switch(wtx->status.status) { - case 1: return QIcon(":/icons/transaction_1"); - case 2: return QIcon(":/icons/transaction_2"); - case 3: return QIcon(":/icons/transaction_3"); - case 4: return QIcon(":/icons/transaction_4"); - default: return QIcon(":/icons/transaction_5"); - }; - case TransactionStatus::Confirmed: - return QIcon(":/icons/transaction_confirmed"); - case TransactionStatus::Conflicted: - return QIcon(":/icons/transaction_conflicted"); - case TransactionStatus::Immature: { - int total = wtx->status.depth + wtx->status.matures_in; - int part = (wtx->status.depth * 4 / total) + 1; - return QIcon(QString(":/icons/transaction_%1").arg(part)); + case TransactionStatus::Immature: { + int total = wtx->status.depth + wtx->status.matures_in; + int part = (wtx->status.depth * 4 / total) + 1; + return QIcon(QString(":/icons/transaction_%1").arg(part)); + } + case TransactionStatus::Confirmed: + return QIcon(":/icons/transaction_confirmed"); + case TransactionStatus::MaturesWarning: + case TransactionStatus::NotAccepted: + return QIcon(":/icons/transaction_0"); + } + } + else + { + switch(wtx->status.status) + { + case TransactionStatus::OpenUntilBlock: + case TransactionStatus::OpenUntilDate: + return QColor(64,64,255); + case TransactionStatus::Offline: + return QColor(192,192,192); + case TransactionStatus::Unconfirmed: + return QIcon(":/icons/transaction_0"); + case TransactionStatus::Confirming: + switch(wtx->status.depth) + { + case 1: return QIcon(":/icons/transaction_1"); + case 2: return QIcon(":/icons/transaction_2"); + case 3: return QIcon(":/icons/transaction_3"); + case 4: return QIcon(":/icons/transaction_4"); + default: return QIcon(":/icons/transaction_5"); + }; + case TransactionStatus::Confirmed: + return QIcon(":/icons/transaction_confirmed"); + case TransactionStatus::Conflicted: + return QIcon(":/icons/transaction_conflicted"); + case TransactionStatus::Immature: { + int total = wtx->status.depth + wtx->status.matures_in; + int part = (wtx->status.depth * 4 / total) + 1; + return QIcon(QString(":/icons/transaction_%1").arg(part)); + } + case TransactionStatus::MaturesWarning: + case TransactionStatus::NotAccepted: + return QIcon(":/icons/transaction_0"); } - case TransactionStatus::MaturesWarning: - case TransactionStatus::NotAccepted: - return QIcon(":/icons/transaction_0"); } return QColor(0,0,0); }