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..453fcb4 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..ede6e24 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..e5b6034 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/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); } 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/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/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/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 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);