diff --git a/README.md b/README.md index 2fe3a7adfc3..474af5a521f 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,30 @@ ![alt text](https://github.com/zerocurrencycoin/Zero/blob/master/art/zero%203d%20mountain.png?raw=true) -[ZERO](https://zerocurrency.io) - [Ny-Alesund:2.0.4](https://github.com/zerocurrencycoin/Zero/releases/tag/v2.0.4) +[ZERO](https://zerocurrency.io) - [Cosmos:3.0.0](https://github.com/zerocurrencycoin/Zero/releases/tag/v3.0.0) ||FAST|| ||DECENTRALISED|| ||ANONYMOUS|| ||SECURE|| ||ASIC RESISTANT|| - LAUNCE DATE: 2017-02-19 GENESIS BLOCK - 19th Feb 2017 11:26:40 - 068cbb5db6bc11be5b93479ea4df41fa7e012e92ca8603c315f9b1a2202205c6 -Download the latest version here - [ZERO - Latest Version - Ny-Alesund:2.0.4](https://github.com/zerocurrencycoin/Zero/releases/tag/v2.0.4) +Download the latest version here - [ZERO - Latest Version - Cosmos:3.0.0](https://github.com/zerocurrencycoin/Zero/releases/tag/v3.0.0) ------------------------------------------ ❓ What is ZERO? -------------- -[ZERO](https://github.com/zerocurrencycoin/Zero/releases/tag/v2.0.4) is a revolutionary cryptocurrency and transaction platform based on Zcash. +[ZERO](https://github.com/zerocurrencycoin/Zero/releases/tag/v3.0.0) is a revolutionary cryptocurrency and transaction platform based on Zcash. -[ZERO](https://github.com/zerocurrencycoin/Zero/releases/tag/v2.0.4) offers total payment confidentiality, while still maintaining a decentralised network using a public blockchain. +[ZERO](https://github.com/zerocurrencycoin/Zero/releases/tag/v3.0.0) offers total payment confidentiality, while still maintaining a decentralised network using a public blockchain. -[ZERO](https://github.com/zerocurrencycoin/Zero/releases/tag/v2.0.4) combines Bitcoin’s security with Zcash’s anonymity and privacy. +[ZERO](https://github.com/zerocurrencycoin/Zero/releases/tag/v3.0.0) combines Bitcoin’s security with Zcash’s anonymity and privacy. -[ZERO](https://github.com/zerocurrencycoin/Zero/releases/tag/v2.0.4) stands out from the competition as a fully working product that has already +[ZERO](https://github.com/zerocurrencycoin/Zero/releases/tag/v3.0.0) stands out from the competition as a fully working product that has already implemented a set of special features not found in any other cryptocurrency. Our main focus as a team and community is to remain as transparent as we can possibly be and to maintain an interactive relationship with everyone involved. We are fully open about the project, listening to all suggestions from investors, miners and supporters. -This software is the [ZERO](https://github.com/zerocurrencycoin/Zero/releases/tag/v2.0.4) node. It downloads and stores the entire history of ZERO's transactions, about 1.2GB at this point. +This software is the [ZERO](https://github.com/zerocurrencycoin/Zero/releases/tag/v3.0.0) node. It downloads and stores the entire history of ZERO's transactions, about 1.2GB at this point. Depending on the speed of your computer and network connection, the synchronization process could take several hours. ------------------------------------------ @@ -122,7 +122,7 @@ See important security warnings on the 📒 Deprecation Policy ------------------ -Disabledeprecation flag has been removed. Old nodes will automatically be shut down and must be upgraded upon reaching the deprecation block height, which will occur approximately 32 weeks (7/1/2019) from the release of v2.0.4. +Disabledeprecation flag has been removed. Old nodes will automatically be shut down and must be upgraded upon reaching the deprecation block height, which will occur approximately 32 weeks (7/1/2019) from the release of v3.0.0. 🔧 Building diff --git a/configure.ac b/configure.ac index 105decc9253..f2efdce0614 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,8 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) -define(_CLIENT_VERSION_MAJOR, 2) +define(_CLIENT_VERSION_MAJOR, 3) define(_CLIENT_VERSION_MINOR, 0) -define(_CLIENT_VERSION_REVISION, 4) +define(_CLIENT_VERSION_REVISION, 0) define(_CLIENT_VERSION_BUILD, 50) define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50))) define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1))) diff --git a/doc/man/zero-cli.1 b/doc/man/zero-cli.1 index 196a047ead8..a0892e8107e 100644 --- a/doc/man/zero-cli.1 +++ b/doc/man/zero-cli.1 @@ -3,7 +3,7 @@ .SH NAME zero-cli \- manual page for zero-cli v2.0.1 .SH DESCRIPTION -Zcash RPC client version v2.0.4 +Zcash RPC client version v3.0.0 .PP In order to ensure you are adequately protecting your privacy when using Zcash, please see . diff --git a/doc/man/zero-tx.1 b/doc/man/zero-tx.1 index cc34c452bcb..fb7a8e018e8 100644 --- a/doc/man/zero-tx.1 +++ b/doc/man/zero-tx.1 @@ -3,7 +3,7 @@ .SH NAME zero-tx \- manual page for zero-tx v2.0.1 .SH DESCRIPTION -Zcash zcash\-tx utility version v2.0.4 +Zcash zcash\-tx utility version v3.0.0 .SS "Usage:" .TP zcash\-tx [options] [commands] diff --git a/doc/man/zerod.1 b/doc/man/zerod.1 index 6f91e69d27a..cdd23162bcd 100644 --- a/doc/man/zerod.1 +++ b/doc/man/zerod.1 @@ -3,7 +3,7 @@ .SH NAME zerod \- manual page for zerod v2.0.1 .SH DESCRIPTION -Zcash Daemon version v2.0.4 +Zcash Daemon version v3.0.0 .PP In order to ensure you are adequately protecting your privacy when using Zcash, please see . diff --git a/src/Makefile.am b/src/Makefile.am index 36598ca7e36..948ac95f957 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -121,6 +121,7 @@ LIBZCASH_H = \ BITCOIN_CORE_H = \ addressindex.h \ spentindex.h \ + zeronode/activezeronode.h \ addrman.h \ alert.h \ amount.h \ @@ -166,6 +167,12 @@ BITCOIN_CORE_H = \ dbwrapper.h \ limitedmap.h \ main.h \ + zeronode/zeronode.h \ + zeronode/payments.h \ + zeronode/budget.h \ + zeronode/zeronode-sync.h \ + zeronode/zeronodeman.h \ + zeronode/zeronodeconfig.h \ memusage.h \ merkleblock.h \ metrics.h \ @@ -174,6 +181,7 @@ BITCOIN_CORE_H = \ net.h \ netbase.h \ noui.h \ + zeronode/obfuscation.h \ policy/fees.h \ pow.h \ prevector.h \ @@ -195,12 +203,15 @@ BITCOIN_CORE_H = \ script/sign.h \ script/standard.h \ serialize.h \ + zeronode/spork.h \ + zeronode/sporkdb.h \ streams.h \ support/allocators/secure.h \ support/allocators/zeroafterfree.h \ support/cleanse.h \ support/events.h \ support/pagelocker.h \ + zeronode/swifttx.h \ sync.h \ threadsafety.h \ timedata.h \ @@ -276,7 +287,11 @@ libbitcoin_server_a_SOURCES = \ rpc/net.cpp \ rpc/rawtransaction.cpp \ rpc/server.cpp \ + rpc/zeronode.cpp \ + rpc/zeronode-budget.cpp \ + rpc/spork.cpp \ script/sigcache.cpp \ + zeronode/sporkdb.cpp \ timedata.cpp \ torcontrol.cpp \ txdb.cpp \ @@ -307,6 +322,10 @@ endif libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ + zeronode/activezeronode.cpp \ + zeronode/obfuscation.cpp \ + utiltest.cpp \ + utiltest.h \ zcbenchmarks.cpp \ zcbenchmarks.h \ wallet/asyncrpcoperation_mergetoaddress.cpp \ @@ -315,6 +334,13 @@ libbitcoin_wallet_a_SOURCES = \ wallet/crypter.cpp \ wallet/db.cpp \ wallet/paymentdisclosure.cpp \ + zeronode/swifttx.cpp \ + zeronode/zeronode.cpp \ + zeronode/budget.cpp \ + zeronode/payments.cpp \ + zeronode/zeronode-sync.cpp \ + zeronode/zeronodeconfig.cpp \ + zeronode/zeronodeman.cpp \ wallet/paymentdisclosuredb.cpp \ wallet/rpcdisclosure.cpp \ wallet/rpcdump.cpp \ @@ -387,6 +413,8 @@ libbitcoin_common_a_SOURCES = \ script/script_error.cpp \ script/sign.cpp \ script/standard.cpp \ + zeronode/spork.cpp \ + zeronode/sporkdb.cpp \ transaction_builder.cpp \ utiltest.cpp \ $(BITCOIN_CORE_H) \ diff --git a/src/bitcoin-cli-res.rc b/src/bitcoin-cli-res.rc index ea0a759d731..49671159cef 100644 --- a/src/bitcoin-cli-res.rc +++ b/src/bitcoin-cli-res.rc @@ -16,8 +16,8 @@ BEGIN BEGIN BLOCK "040904E4" // U.S. English - multilingual (hex) BEGIN - VALUE "CompanyName", "Zcash" - VALUE "FileDescription", "zero-cli (JSON-RPC client for Zcash)" + VALUE "CompanyName", "Zero" + VALUE "FileDescription", "zero-cli (JSON-RPC client for Zero)" VALUE "FileVersion", VER_FILEVERSION_STR VALUE "InternalName", "zero-cli" VALUE "LegalCopyright", COPYRIGHT_STR diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index cdfcf656dd3..a383887a61b 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -77,10 +77,10 @@ static int AppInitRPC(int argc, char* argv[]) // ParseParameters(argc, argv); if (argc<2 || mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help") || mapArgs.count("-version")) { - std::string strUsage = _("Zcash RPC client version") + " " + FormatFullVersion() + "\n" + PrivacyInfo(); + std::string strUsage = _("Zero RPC client version") + " " + FormatFullVersion() + "\n" + PrivacyInfo(); if (!mapArgs.count("-version")) { strUsage += "\n" + _("Usage:") + "\n" + - " zero-cli [options] [params] " + _("Send command to Zcash") + "\n" + + " zero-cli [options] [params] " + _("Send command to Zero") + "\n" + " zero-cli [options] help " + _("List commands") + "\n" + " zero-cli [options] help " + _("Get help for a command") + "\n"; diff --git a/src/bitcoin-tx-res.rc b/src/bitcoin-tx-res.rc index d8daac85044..08a3ba0923b 100644 --- a/src/bitcoin-tx-res.rc +++ b/src/bitcoin-tx-res.rc @@ -16,8 +16,8 @@ BEGIN BEGIN BLOCK "040904E4" // U.S. English - multilingual (hex) BEGIN - VALUE "CompanyName", "Zcash" - VALUE "FileDescription", "zero-tx (CLI Zcash transaction editor utility)" + VALUE "CompanyName", "Zero" + VALUE "FileDescription", "zero-tx (CLI Zero transaction editor utility)" VALUE "FileVersion", VER_FILEVERSION_STR VALUE "InternalName", "zero-tx" VALUE "LegalCopyright", COPYRIGHT_STR diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 20059764c2c..e69ae7114be 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -48,10 +48,10 @@ static int AppInitRawTx(int argc, char* argv[]) if (argc<2 || mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help")) { // First part of help message is specific to this utility - std::string strUsage = _("Zcash zero-tx utility version") + " " + FormatFullVersion() + "\n\n" + + std::string strUsage = _("Zero zero-tx utility version") + " " + FormatFullVersion() + "\n\n" + _("Usage:") + "\n" + - " zero-tx [options] [commands] " + _("Update hex-encoded zcash transaction") + "\n" + - " zero-tx [options] -create [commands] " + _("Create hex-encoded zcash transaction") + "\n" + + " zero-tx [options] [commands] " + _("Update hex-encoded zero transaction") + "\n" + + " zero-tx [options] -create [commands] " + _("Create hex-encoded zero transaction") + "\n" + "\n"; fprintf(stdout, "%s", strUsage.c_str()); diff --git a/src/bitcoind-res.rc b/src/bitcoind-res.rc index f8f7acea2cc..43e5719edcb 100644 --- a/src/bitcoind-res.rc +++ b/src/bitcoind-res.rc @@ -16,8 +16,8 @@ BEGIN BEGIN BLOCK "040904E4" // U.S. English - multilingual (hex) BEGIN - VALUE "CompanyName", "Zcash" - VALUE "FileDescription", "zerod (Zcash node with a JSON-RPC server)" + VALUE "CompanyName", "Zero" + VALUE "FileDescription", "zerod (Zero node with a JSON-RPC server)" VALUE "FileVersion", VER_FILEVERSION_STR VALUE "InternalName", "zerod" VALUE "LegalCopyright", COPYRIGHT_STR diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 8863fc1f034..0d1a55d77eb 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -7,6 +7,7 @@ #include "rpc/server.h" #include "init.h" #include "main.h" +#include "zeronode/zeronodeconfig.h" #include "noui.h" #include "scheduler.h" #include "util.h" @@ -72,7 +73,7 @@ bool AppInit(int argc, char* argv[]) // Process help and version before taking care about datadir if (mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help") || mapArgs.count("-version")) { - std::string strUsage = _("Zcash Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n" + PrivacyInfo(); + std::string strUsage = _("Zero Daemon") + " " + _("version") + " " + FormatFullVersion() + "\n" + PrivacyInfo(); if (mapArgs.count("-version")) { @@ -81,7 +82,7 @@ bool AppInit(int argc, char* argv[]) else { strUsage += "\n" + _("Usage:") + "\n" + - " zerod [options] " + _("Start Zcash Daemon") + "\n"; + " zerod [options] " + _("Start Zero Daemon") + "\n"; strUsage += "\n" + HelpMessage(HMM_BITCOIND); } @@ -111,7 +112,7 @@ bool AppInit(int argc, char* argv[]) "\n" "You can look at the example configuration file for suggestions of default\n" "options that you may want to change. It should be in one of these locations,\n" - "depending on how you installed Zcash:\n") + + "depending on how you installed Zero:\n") + _("- Source code: %s\n" "- .deb package: %s\n")).c_str(), GetConfigFile().string().c_str(), @@ -128,10 +129,19 @@ bool AppInit(int argc, char* argv[]) return false; } + //Start Zeronode + // parse zeronode.conf + std::string strErr; + if (!zeronodeConfig.read(strErr)) { + fprintf(stderr, "Error reading zeronode configuration file: %s\n", strErr.c_str()); + return false; + } + //End Zeronode + // Command-line RPC bool fCommandLine = false; for (int i = 1; i < argc; i++) - if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "zcash:")) + if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "zero:")) fCommandLine = true; if (fCommandLine) @@ -143,7 +153,7 @@ bool AppInit(int argc, char* argv[]) fDaemon = GetBoolArg("-daemon", false); if (fDaemon) { - fprintf(stdout, "Zcash server starting\n"); + fprintf(stdout, "Zero server starting\n"); // Daemonize pid_t pid = fork(); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 3a5af3bc264..958b36a136f 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -82,7 +82,7 @@ class CMainParams : public CChainParams { CMainParams() { strNetworkID = "main"; strCurrencyUnits = "ZER"; - bip44CoinType = 133; // As registered in https://github.com/satoshilabs/slips/blob/master/slip-0044.md + bip44CoinType = 323; // As registered in https://github.com/satoshilabs/slips/blob/master/slip-0044.md consensus.fCoinbaseMustBeProtected = true; consensus.nFeeStartBlockHeight = 412300; consensus.nSubsidyHalvingInterval = 800000; @@ -106,7 +106,9 @@ class CMainParams : public CChainParams { consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nActivationHeight = 492850; consensus.vUpgrades[Consensus::UPGRADE_SAPLING].nProtocolVersion = 170007; consensus.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight = 492850; - + consensus.vUpgrades[Consensus::UPGRADE_COSMOS].nProtocolVersion = 170008; + consensus.vUpgrades[Consensus::UPGRADE_COSMOS].nActivationHeight = 620450; + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); @@ -126,6 +128,13 @@ class CMainParams : public CChainParams { nEquihashN = N; nEquihashK = K; + //Start Zeronode + nZeronodeCountDrift = 0; + strSporkKey = "0477f4d5094e70c26bf998ba0d0e06af8c31b399c5b794895da2158dac086260353c50eaf477e7c5ec6b87349fc63bacdd56f0ffe4dcc112dca71d8335cd1ad2c1"; + strZeronodeDummyAddress = "t1TLNF3seMZennWmmxik8r1PVEKj5zudgRw"; + nBudget_Fee_Confirmations = 6; // Number of confirmations for the finalization fee + //End Zeronode + genesis = CreateGenesisBlock( 1487500000, uint256S("4c697665206c6f6e6720616e642070726f7370657221014592005a64336e336b"), @@ -140,11 +149,16 @@ class CMainParams : public CChainParams { vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); vSeeds.clear(); + vSeeds.push_back(CDNSSeedData("zerocurrency0", "seed0.zerocurrency.io")); vSeeds.push_back(CDNSSeedData("zerocurrency1", "seed1.zerocurrency.io")); vSeeds.push_back(CDNSSeedData("zerocurrency2", "seed2.zerocurrency.io")); vSeeds.push_back(CDNSSeedData("zerocurrency3", "seed3.zerocurrency.io")); vSeeds.push_back(CDNSSeedData("zerocurrency4", "seed4.zerocurrency.io")); vSeeds.push_back(CDNSSeedData("zerocurrency5", "seed5.zerocurrency.io")); + vSeeds.push_back(CDNSSeedData("zerocurrency6", "seed6.zerocurrency.io")); + vSeeds.push_back(CDNSSeedData("zerocurrency7", "seed7.zerocurrency.io")); + vSeeds.push_back(CDNSSeedData("zerocurrency8", "seed8.zerocurrency.io")); + vSeeds.push_back(CDNSSeedData("zerocurrency9", "seed9.zerocurrency.io")); // guarantees the first 2 characters, when base58 encoded, are "t1" base58Prefixes[PUBKEY_ADDRESS] = {0x1C,0xB8}; @@ -175,10 +189,19 @@ class CMainParams : public CChainParams { checkpointData = (CCheckpointData) { boost::assign::map_list_of - ( 0, consensus.hashGenesisBlock), - genesis.nTime, - 0, - 0 + ( 0, consensus.hashGenesisBlock) + ( 60000, uint256S("0x00002ae1f476c997f3800d6c7fc733efaa0fc6172d91075724a60a9fd42dcf3a")) + ( 140000, uint256S("0x00000eb02cca5b05f4be1ee4016790e5b1a3817eb12b1aba605024602665ce7e")) + ( 200000, uint256S("0x0000090d945b23a43b757d57f8f396ac748861f22bf1d8914cba440fd59a5c43")) + ( 300000, uint256S("0x000001e0b15c1f74af391c39bcb2d61ea879238b53ba738a45303cccd84c2c3f")) + ( 400000, uint256S("0x000001ebcc62c257dd00d70bb7fbc210580875e8dbc9f1f9c9aafdb4dc1d8a4e")) + ( 500000, uint256S("0x000000c642fda400a464c50dcf310e65efa2627799b9b0c378524205ba2307b7")) + ( 550000, uint256S("0x000003cb1f3c128819bdf84632fd31058014542af64eea8959d5139fc26c21cd")), + 1553590617, // * UNIX timestamp of last checkpoint block + 1067556, // * total number of transactions between genesis and last checkpoint + // (the tx=... number in the SetBestChain debug.log lines) + 1118 // * estimated number of transactions per day after checkpoint + // total number of tx / (checkpoint block height / (24 * 24)) }; // Founders reward script expects a vector of 2-of-3 multisig addresses @@ -232,6 +255,8 @@ class CTestNetParams : public CChainParams { consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nActivationHeight = 50; consensus.vUpgrades[Consensus::UPGRADE_SAPLING].nProtocolVersion = 170007; consensus.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight = 50; + consensus.vUpgrades[Consensus::UPGRADE_COSMOS].nProtocolVersion = 170008; + consensus.vUpgrades[Consensus::UPGRADE_COSMOS].nActivationHeight =47925; // The best chain should have at least this much work. @@ -250,6 +275,13 @@ class CTestNetParams : public CChainParams { nEquihashN = N; nEquihashK = K; + //Start Zeronode + nZeronodeCountDrift = 0; + strSporkKey = "04f249a25f6708898afead4e01fc726269ffbdcbbecad7f675ed2470f68571e57ac32bde7111781e476b0c0256cc5e7b71cc5fd56fcffbfb1ead0cb6fe89d91303"; + strZeronodeDummyAddress = "tmWuQ8Yh3pHDa8MingmN8ECPRBxo2n8uZRs"; + nBudget_Fee_Confirmations = 6; // Number of confirmations for the finalization fee + //End Zeronode + genesis = CreateGenesisBlock( 1542244402, uint256S("0000000000000000000000000000000000000000000000000000000000000007"), @@ -370,6 +402,9 @@ class CRegTestParams : public CChainParams { consensus.vUpgrades[Consensus::UPGRADE_SAPLING].nProtocolVersion = 170007; consensus.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.vUpgrades[Consensus::UPGRADE_COSMOS].nProtocolVersion = 170008; + consensus.vUpgrades[Consensus::UPGRADE_COSMOS].nActivationHeight = + Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; // The best chain should have at least this much work. @@ -388,6 +423,13 @@ class CRegTestParams : public CChainParams { nEquihashN = N; nEquihashK = K; + //Start Zeronode + nZeronodeCountDrift = 0; + strSporkKey = "045da9271f5d9df405d9e83c7c7e62e9c831cc85c51ffaa6b515c4f9c845dec4bf256460003f26ba9d394a17cb57e6759fe231eca75b801c20bccd19cbe4b7942d"; + strZeronodeDummyAddress = "s1eQnJdoWDhKhxDrX8ev3aFjb1J6ZwXCxUT"; + nBudget_Fee_Confirmations = 6; // Number of confirmations for the finalization fee + //End Zeronode + genesis = CreateGenesisBlock( 1531037936, uint256S("0000000000000000000000000000000000000000000000000000000000000001"), diff --git a/src/chainparams.h b/src/chainparams.h index ce5c1d6a179..25adae3aea1 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -98,6 +98,17 @@ class CChainParams const std::string& Bech32HRP(Bech32Type type) const { return bech32HRPs[type]; } const std::vector& FixedSeeds() const { return vFixedSeeds; } const CCheckpointData& Checkpoints() const { return checkpointData; } + + /** Start Zeronode **/ + /** The zeronode count that we will allow the see-saw reward payments to be off by */ + int ZeronodeCountDrift() const { return nZeronodeCountDrift; } + std::string SporkKey() const { return strSporkKey; } + std::string ZeronodeDummyAddress() const { return strZeronodeDummyAddress; } + /** Headers first syncing is disabled */ + bool HeadersFirstSyncingActive() const { return fHeadersFirstSyncingActive; }; + int64_t Budget_Fee_Confirmations() const { return nBudget_Fee_Confirmations; } + /**End Zeronode **/ + /** Return the founder's reward address and script for a given block height */ std::string GetFoundersRewardAddressAtHeight(int height) const; CScript GetFoundersRewardScriptAtHeight(int height) const; @@ -128,6 +139,15 @@ class CChainParams bool fRequireStandard = false; bool fMineBlocksOnDemand = false; bool fTestnetToBeDeprecatedFieldRPC = false; + + /** Start Zeronode **/ + int nZeronodeCountDrift; + std::string strSporkKey; + bool fHeadersFirstSyncingActive; + std::string strZeronodeDummyAddress; + int64_t nBudget_Fee_Confirmations; + /** Start Zeronode **/ + CCheckpointData checkpointData; std::vector vFoundersRewardAddress; diff --git a/src/clientversion.cpp b/src/clientversion.cpp index 79b85431873..20be7ee31c0 100644 --- a/src/clientversion.cpp +++ b/src/clientversion.cpp @@ -19,7 +19,7 @@ * for both bitcoind and bitcoin-core, to make it harder for attackers to * target servers or GUI users specifically. */ -const std::string CLIENT_NAME("Ny-Alesund"); +const std::string CLIENT_NAME("Cosmos"); /** * Client version number diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h index ddd2397f884..0846e38bc64 100644 --- a/src/consensus/consensus.h +++ b/src/consensus/consensus.h @@ -25,6 +25,7 @@ static const unsigned int MAX_BLOCK_SIGOPS = 40000; /** The maximum size of a transaction (network rule) */ static const unsigned int MAX_TX_SIZE_BEFORE_SAPLING = 100000; static const unsigned int MAX_TX_SIZE_AFTER_SAPLING = MAX_BLOCK_SIZE; +static const unsigned int MAX_TX_SIZE_COSMOS = 200000; /** Coinbase transaction outputs can only be spent after this number of new blocks (network rule) */ static const int COINBASE_MATURITY = 720; /** The minimum value which is invalid for expiry height, used by CTransaction and CMutableTransaction */ diff --git a/src/consensus/params.h b/src/consensus/params.h index 24390f7085c..ccb4a9e957d 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -26,6 +26,7 @@ enum UpgradeIndex { UPGRADE_TESTDUMMY, UPGRADE_OVERWINTER, UPGRADE_SAPLING, + UPGRADE_COSMOS, // NOTE: Also add new upgrades to NetworkUpgradeInfo in upgrades.cpp MAX_NETWORK_UPGRADES }; diff --git a/src/consensus/upgrades.cpp b/src/consensus/upgrades.cpp index 3e5cac4d37b..49151bbd676 100644 --- a/src/consensus/upgrades.cpp +++ b/src/consensus/upgrades.cpp @@ -22,12 +22,17 @@ const struct NUInfo NetworkUpgradeInfo[Consensus::MAX_NETWORK_UPGRADES] = { { /*.nBranchId =*/ 0x6f76727a, /*.strName =*/ "Overwinter", - /*.strInfo =*/ "See https://z.cash/upgrade/overwinter.html for details.", + /*.strInfo =*/ "Overwinter Upgrade", }, { /*.nBranchId =*/ 0x7361707a, /*.strName =*/ "Sapling", - /*.strInfo =*/ "See https://z.cash/upgrade/sapling.html for details.", + /*.strInfo =*/ "Sapling Upgrade.", + }, + { + /*.nBranchId =*/ 0x7361707a, + /*.strName =*/ "Cosmos", + /*.strInfo =*/ "Cosmos Upgrade.", } }; diff --git a/src/deprecation.h b/src/deprecation.h index 0d946cd8ec7..8cffc956507 100644 --- a/src/deprecation.h +++ b/src/deprecation.h @@ -8,7 +8,7 @@ // Deprecation policy: // * Shut down 26 weeks' worth of blocks after the estimated release block height. // * A warning is shown during the 4 weeks' worth of blocks prior to shut down. -static const int APPROX_RELEASE_HEIGHT = 458500; +static const int APPROX_RELEASE_HEIGHT = 593110; static const int WEEKS_UNTIL_DEPRECATION = 32; //Fixing zero day size static const int DEPRECATION_HEIGHT = APPROX_RELEASE_HEIGHT + (WEEKS_UNTIL_DEPRECATION * 7 * 24 * 30); diff --git a/src/init.cpp b/src/init.cpp index e21067adaa3..c2acea5a0ca 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -9,6 +9,7 @@ #include "init.h" #include "crypto/common.h" +#include "zeronode/activezeronode.h" #include "addrman.h" #include "amount.h" #include "checkpoints.h" @@ -22,6 +23,10 @@ #include "key_io.h" #endif #include "main.h" +#include "zeronode/budget.h" +#include "zeronode/payments.h" +#include "zeronode/zeronodeconfig.h" +#include "zeronode/zeronodeman.h" #include "metrics.h" #include "miner.h" #include "net.h" @@ -29,6 +34,8 @@ #include "rpc/register.h" #include "script/standard.h" #include "script/sigcache.h" +#include "zeronode/spork.h" +#include "zeronode/sporkdb.h" #include "scheduler.h" #include "txdb.h" #include "torcontrol.h" @@ -208,6 +215,9 @@ void Shutdown() #endif StopNode(); StopTorControl(); + DumpZeronodes(); + DumpBudgets(); + DumpZeronodePayments(); UnregisterNodeSignals(GetNodeSignals()); if (fFeeEstimatesInitialized) @@ -234,6 +244,8 @@ void Shutdown() pcoinsdbview = NULL; delete pblocktree; pblocktree = NULL; + delete pSporkDB; + pSporkDB = NULL; } #ifdef ENABLE_WALLET if (pwalletMain) @@ -487,6 +499,13 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-shrinkdebugfile", _("Shrink debug.log file on client startup (default: 1 when no -debug)")); strUsage += HelpMessageOpt("-testnet", _("Use the test network")); + strUsage += HelpMessageGroup(_("Zeronode options:")); + strUsage += HelpMessageOpt("-zeronode=", strprintf(_("Enable the client to act as a zeronode (0-1, default: %u)"), 0)); + strUsage += HelpMessageOpt("-znconf=", strprintf(_("Specify zeronode configuration file (default: %s)"), "zeronode.conf")); + strUsage += HelpMessageOpt("-znconflock=", strprintf(_("Lock zeronodes from zeronode configuration file (default: %u)"), 1)); + strUsage += HelpMessageOpt("-zeronodeprivkey=", _("Set the zeronode private key")); + strUsage += HelpMessageOpt("-zeronodeaddr=", strprintf(_("Set external address:port to get to this zeronode (example: %s)"), "128.127.106.235:60020")); + strUsage += HelpMessageOpt("-budgetvotemode=", _("Change automatic finalized budget voting behavior. mode=auto: Vote for only exact finalized budget match to my generated budget. (string, default: auto)")); strUsage += HelpMessageGroup(_("Node relay options:")); strUsage += HelpMessageOpt("-datacarrier", strprintf(_("Relay and mine data carrier transactions (default: %u)"), 1)); strUsage += HelpMessageOpt("-datacarriersize", strprintf(_("Maximum size of data in data carrier transactions we relay and mine (default: %u)"), MAX_OP_RETURN_RELAY)); @@ -538,6 +557,8 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-metricsrefreshtime", strprintf(_("Number of seconds between metrics refreshes (default: %u if running in a console, %u otherwise)"), 1, 600)); } + strUsage += HelpMessageOpt("-sporkkey=", _("Enable spork administration functionality with the appropriate private key.")); + return strUsage; } @@ -701,9 +722,9 @@ static void ZC_LoadParams( boost::filesystem::exists(sprout_groth16) )) { uiInterface.ThreadSafeMessageBox(strprintf( - _("Cannot find the Zcash network parameters in the following directory:\n" + _("Cannot find the Zero network parameters in the following directory:\n" "%s\n" - "Please run 'zcash-fetch-params' or './zcutil/fetch-params.sh' and then restart."), + "Please run 'zero-fetch-params' or './zcutil/fetch-params.sh' and then restart."), ZC_GetParamsDir()), "", CClientUIInterface::MSG_ERROR); StartShutdown(); @@ -854,7 +875,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) fLogIPs = GetBoolArg("-logips", false); LogPrintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); - LogPrintf("Zcash version %s (%s)\n", FormatFullVersion(), CLIENT_DATE); + LogPrintf("Zero version %s (%s)\n", FormatFullVersion(), CLIENT_DATE); // when specifying an explicit binding address, you want to listen on it // even when -connect or -proxy is specified @@ -1153,7 +1174,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // Sanity check if (!InitSanityCheck()) - return InitError(_("Initialization sanity check failed. Zcash is shutting down.")); + return InitError(_("Initialization sanity check failed. Zero is shutting down.")); std::string strDataDir = GetDataDir().string(); #ifdef ENABLE_WALLET @@ -1169,9 +1190,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) try { static boost::interprocess::file_lock lock(pathLockFile.string().c_str()); if (!lock.try_lock()) - return InitError(strprintf(_("Cannot obtain a lock on data directory %s. Zcash is probably already running."), strDataDir)); + return InitError(strprintf(_("Cannot obtain a lock on data directory %s. Zero is probably already running."), strDataDir)); } catch(const boost::interprocess::interprocess_exception& e) { - return InitError(strprintf(_("Cannot obtain a lock on data directory %s. Zcash is probably already running.") + " %s.", strDataDir, e.what())); + return InitError(strprintf(_("Cannot obtain a lock on data directory %s. Zero is probably already running.") + " %s.", strDataDir, e.what())); } #ifndef WIN32 @@ -1201,6 +1222,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) threadGroup.create_thread(&ThreadScriptCheck); } + if (mapArgs.count("-sporkkey")) // spork priv key + { + if (!sporkManager.SetPrivKey(GetArg("-sporkkey", ""))) + return InitError(_("Unable to sign spork message, wrong key?")); + } + // Start the lightweight task scheduler thread CScheduler::Function serviceLoop = boost::bind(&CScheduler::serviceQueue, &scheduler); threadGroup.create_thread(boost::bind(&TraceThread, "scheduler", serviceLoop)); @@ -1221,7 +1248,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) libsnark::inhibit_profiling_info = true; libsnark::inhibit_profiling_counters = true; - // Initialize Zcash circuit parameters + // Initialize Zero circuit parameters ZC_LoadParams(chainparams); if (GetBoolArg("-savesproutr1cs", false)) { @@ -1462,7 +1489,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) bool fReset = fReindex; std::string strLoadError; - uiInterface.InitMessage(_("Loading block index...")); + nStart = GetTimeMillis(); do { @@ -1472,7 +1499,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) delete pcoinsdbview; delete pcoinscatcher; delete pblocktree; + delete pSporkDB; + pSporkDB = new CSporkDB(0, false, false); pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex); pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex); pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); @@ -1485,6 +1514,10 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) CleanupBlockRevFiles(); } + uiInterface.InitMessage(_("Loading sporks...")); + LoadSporksFromDB(); + + uiInterface.InitMessage(_("Loading block index...")); if (!LoadBlockIndex()) { strLoadError = _("Error loading block database"); break; @@ -1624,7 +1657,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) else if (nLoadWalletRet == DB_NEED_REWRITE) { - strErrors << _("Wallet needed to be rewritten: restart Zcash to complete") << "\n"; + strErrors << _("Wallet needed to be rewritten: restart Zero to complete") << "\n"; LogPrintf("%s", strErrors.str()); return InitError(strErrors.str()); } @@ -1731,10 +1764,10 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) #ifdef ENABLE_MINING #ifndef ENABLE_WALLET if (GetBoolArg("-minetolocalwallet", false)) { - return InitError(_("Zcash was not built with wallet support. Set -minetolocalwallet=0 to use -mineraddress, or rebuild Zcash with wallet support.")); + return InitError(_("Zero was not built with wallet support. Set -minetolocalwallet=0 to use -mineraddress, or rebuild Zero with wallet support.")); } if (GetArg("-mineraddress", "").empty() && GetBoolArg("-gen", false)) { - return InitError(_("Zcash was not built with wallet support. Set -mineraddress, or rebuild Zcash with wallet support.")); + return InitError(_("Zero was not built with wallet support. Set -mineraddress, or rebuild Zero with wallet support.")); } #endif // !ENABLE_WALLET @@ -1807,6 +1840,148 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) MilliSleep(10); } + + // ********************************************************* Step 10a: setup Zeronode + + + uiInterface.InitMessage(_("Loading zeronode cache...")); + + CZeronodeDB zndb; + CZeronodeDB::ReadResult readResult = zndb.Read(znodeman); + if (readResult == CZeronodeDB::FileError) + LogPrintf("Missing zeronode cache file - zncache.dat, will try to recreate\n"); + else if (readResult != CZeronodeDB::Ok) { + LogPrintf("Error reading zncache.dat: "); + if (readResult == CZeronodeDB::IncorrectFormat) + LogPrintf("magic is ok but data has invalid format, will try to recreate\n"); + else + LogPrintf("file format is unknown or invalid, please fix it manually\n"); + } + + uiInterface.InitMessage(_("Loading budget cache...")); + + CBudgetDB budgetdb; + CBudgetDB::ReadResult readResult2 = budgetdb.Read(budget); + + if (readResult2 == CBudgetDB::FileError) + LogPrintf("Missing budget cache - budget.dat, will try to recreate\n"); + else if (readResult2 != CBudgetDB::Ok) { + LogPrintf("Error reading budget.dat: "); + if (readResult2 == CBudgetDB::IncorrectFormat) + LogPrintf("magic is ok but data has invalid format, will try to recreate\n"); + else + LogPrintf("file format is unknown or invalid, please fix it manually\n"); + } + + //flag our cached items so we send them to our peers + budget.ResetSync(); + budget.ClearSeen(); + + + uiInterface.InitMessage(_("Loading zeronode payment cache...")); + + CZeronodePaymentDB znpayments; + CZeronodePaymentDB::ReadResult readResult3 = znpayments.Read(zeronodePayments); + + if (readResult3 == CZeronodePaymentDB::FileError) + LogPrintf("Missing zeronode payment cache - znpayments.dat, will try to recreate\n"); + else if (readResult3 != CZeronodePaymentDB::Ok) { + LogPrintf("Error reading znpayments.dat: "); + if (readResult3 == CZeronodePaymentDB::IncorrectFormat) + LogPrintf("magic is ok but data has invalid format, will try to recreate\n"); + else + LogPrintf("file format is unknown or invalid, please fix it manually\n"); + } + + fZeroNode = GetBoolArg("-zeronode", false); + + if ((fZeroNode || zeronodeConfig.getCount() > -1) && fTxIndex == false) { + return InitError("Enabling Zeronode support requires turning on transaction indexing." + "Please add txindex=1 to your configuration and start with -reindex"); + } + + if (fZeroNode) { + LogPrintf("IS MASTER NODE\n"); + strZeroNodeAddr = GetArg("-zeronodeaddr", ""); + + LogPrintf(" addr %s\n", strZeroNodeAddr.c_str()); + + if (!strZeroNodeAddr.empty()) { + CService addrTest = CService(strZeroNodeAddr); + if (!addrTest.IsValid()) { + return InitError("Invalid -zeronodeaddr address: " + strZeroNodeAddr); + } + } + + strZeroNodePrivKey = GetArg("-zeronodeprivkey", ""); + if (!strZeroNodePrivKey.empty()) { + std::string errorMessage; + + CKey key; + CPubKey pubkey; + + if (!obfuScationSigner.SetKey(strZeroNodePrivKey, errorMessage, key, pubkey)) { + return InitError(_("Invalid zeronodeprivkey. Please see documenation.")); + } + + activeZeronode.pubKeyZeronode = pubkey; + + } else { + return InitError(_("You must specify a zeronodeprivkey in the configuration. Please see documentation for help.")); + } + } + + //get the mode of budget voting for this zeronode + strBudgetMode = GetArg("-budgetvotemode", "auto"); + + if (GetBoolArg("-znconflock", true) && pwalletMain) { + LOCK(pwalletMain->cs_wallet); + LogPrintf("Locking Zeronodes:\n"); + uint256 znTxHash; + BOOST_FOREACH (CZeronodeConfig::CZeronodeEntry zne, zeronodeConfig.getEntries()) { + LogPrintf(" %s %s\n", zne.getTxHash(), zne.getOutputIndex()); + znTxHash.SetHex(zne.getTxHash()); + COutPoint outpoint = COutPoint(znTxHash, boost::lexical_cast(zne.getOutputIndex())); + pwalletMain->LockCoin(outpoint); + } + } + + //lite mode disables all Zeronode and Obfuscation related functionality + fLiteMode = GetBoolArg("-litemode", false); + if (fZeroNode && fLiteMode) { + return InitError("You can not start a zeronode in litemode"); + } + + fEnableSwiftTX = GetBoolArg("-enableswifttx", fEnableSwiftTX); + nSwiftTXDepth = GetArg("-swifttxdepth", nSwiftTXDepth); + nSwiftTXDepth = std::min(std::max(nSwiftTXDepth, 0), 60); + + LogPrintf("fLiteMode %d\n", fLiteMode); + LogPrintf("nSwiftTXDepth %d\n", nSwiftTXDepth); + LogPrintf("Budget Mode %s\n", strBudgetMode.c_str()); + + /* Denominations + + A note about convertability. Within Obfuscation pools, each denomination + is convertable to another. + + For example: + 1XLR+1000 == (.1XLR+100)*10 + 10XLR+10000 == (1XLR+1000)*10 + */ + obfuScationDenominations.push_back((10000 * COIN) + 10000000); + obfuScationDenominations.push_back((1000 * COIN) + 1000000); + obfuScationDenominations.push_back((100 * COIN) + 100000); + obfuScationDenominations.push_back((10 * COIN) + 10000); + obfuScationDenominations.push_back((1 * COIN) + 1000); + obfuScationDenominations.push_back((.1 * COIN) + 100); + /* Disabled till we need them + obfuScationDenominations.push_back( (.01 * COIN)+10 ); + obfuScationDenominations.push_back( (.001 * COIN)+1 ); + */ + + threadGroup.create_thread(boost::bind(&ThreadCheckObfuScationPool)); + // ********************************************************* Step 11: start node if (!CheckDiskSpace()) diff --git a/src/main.cpp b/src/main.cpp index e293994e70a..66aee65de06 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,10 +17,17 @@ #include "consensus/validation.h" #include "deprecation.h" #include "init.h" +#include "zeronode/budget.h" +#include "zeronode/payments.h" +#include "zeronode/zeronodeman.h" #include "merkleblock.h" #include "metrics.h" #include "net.h" +#include "zeronode/obfuscation.h" #include "pow.h" +#include "zeronode/spork.h" +#include "zeronode/sporkdb.h" +#include "zeronode/swifttx.h" #include "txdb.h" #include "txmempool.h" #include "ui_interface.h" @@ -96,6 +103,7 @@ struct COrphanTx { }; map mapOrphanTransactions GUARDED_BY(cs_main);; map > mapOrphanTransactionsByPrev GUARDED_BY(cs_main);; +map mapRejectedBlocks GUARDED_BY(cs_main);; void EraseOrphansFor(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** @@ -108,7 +116,7 @@ static void CheckBlockIndex(); /** Constant stuff for coinbase transactions we create: */ CScript COINBASE_FLAGS; -const string strMessageMagic = "Zcash Signed Message:\n"; +const string strMessageMagic = "Zero Signed Message:\n"; // Internal stuff namespace { @@ -566,6 +574,7 @@ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& loc CCoinsViewCache *pcoinsTip = NULL; CBlockTreeDB *pblocktree = NULL; +CSporkDB* pSporkDB = NULL; ////////////////////////////////////////////////////////////////////////////// // @@ -878,6 +887,59 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in return nSigOps; } +int GetInputAge(CTxIn& vin) +{ + CCoinsView viewDummy; + CCoinsViewCache view(&viewDummy); + { + LOCK(mempool.cs); + CCoinsViewMemPool viewMempool(pcoinsTip, mempool); + view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view + + const CCoins* coins = view.AccessCoins(vin.prevout.hash); + + if (coins) { + if (coins->nHeight < 0) return 0; + return (chainActive.Tip()->nHeight + 1) - coins->nHeight; + } else + return -1; + } +} + +int GetInputAgeIX(uint256 nTXHash, CTxIn& vin) +{ + int sigs = 0; + int nResult = GetInputAge(vin); + if (nResult < 0) nResult = 0; + + if (nResult < 6) { + std::map::iterator i = mapTxLocks.find(nTXHash); + if (i != mapTxLocks.end()) { + sigs = (*i).second.CountSignatures(); + } + if (sigs >= SWIFTTX_SIGNATURES_REQUIRED) { + return nSwiftTXDepth + nResult; + } + } + + return -1; +} + +int GetIXConfirmations(uint256 nTXHash) +{ + int sigs = 0; + + std::map::iterator i = mapTxLocks.find(nTXHash); + if (i != mapTxLocks.end()) { + sigs = (*i).second.CountSignatures(); + } + if (sigs >= SWIFTTX_SIGNATURES_REQUIRED) { + return nSwiftTXDepth; + } + + return 0; +} + /** * Check a transaction contextually against a set of consensus rules valid at a given block height. * @@ -896,6 +958,7 @@ bool ContextualCheckTransaction( { bool overwinterActive = NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER); bool saplingActive = NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING); + bool cosmosActive = NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_COSMOS); bool isSprout = !overwinterActive; // If Sprout rules apply, reject transactions which are intended for Overwinter and beyond @@ -976,6 +1039,14 @@ bool ContextualCheckTransaction( REJECT_INVALID, "bad-txns-oversize"); } + if (cosmosActive) { + // Size limits + BOOST_STATIC_ASSERT(MAX_BLOCK_SIZE > MAX_TX_SIZE_COSMOS); // sanity + if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_TX_SIZE_COSMOS) + return state.DoS(100, error("ContextualCheckTransaction(): size limits failed"), + REJECT_INVALID, "bad-txns-oversize"); + } + uint256 dataToBeSigned; if (!tx.vjoinsplit.empty() || @@ -1147,6 +1218,7 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio // Size limits BOOST_STATIC_ASSERT(MAX_BLOCK_SIZE >= MAX_TX_SIZE_AFTER_SAPLING); // sanity BOOST_STATIC_ASSERT(MAX_TX_SIZE_AFTER_SAPLING > MAX_TX_SIZE_BEFORE_SAPLING); // sanity + BOOST_STATIC_ASSERT(MAX_TX_SIZE_COSMOS > MAX_TX_SIZE_BEFORE_SAPLING); // sanity if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_TX_SIZE_AFTER_SAPLING) return state.DoS(100, error("CheckTransaction(): size limits failed"), REJECT_INVALID, "bad-txns-oversize"); @@ -1615,6 +1687,206 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa return true; } +bool AcceptableInputs(CTxMemPool& pool, CValidationState& state, const CTransaction& tx, bool fLimitFree, bool* pfMissingInputs, bool fRejectInsaneFee) +{ + AssertLockHeld(cs_main); + if (pfMissingInputs) + *pfMissingInputs = false; + + auto verifier = libzcash::ProofVerifier::Disabled(); + if (!CheckTransaction(tx, state, verifier)) + return error("AcceptableInputs: : CheckTransaction failed"); + + // Coinbase is only valid in a block, not as a loose transaction + if (tx.IsCoinBase()) + return state.DoS(100, error("AcceptableInputs: : coinbase as individual tx"), + REJECT_INVALID, "coinbase"); + + // Rather not work on nonstandard transactions (unless -testnet/-regtest) + string reason; + // for any real tx this will be checked on AcceptToMemoryPool anyway + // if (Params().RequireStandard() && !IsStandardTx(tx, reason)) + // return state.DoS(0, + // error("AcceptableInputs : nonstandard transaction: %s", reason), + // REJECT_NONSTANDARD, reason); + + // is it already in the memory pool? + uint256 hash = tx.GetHash(); + if (pool.exists(hash)) + return false; + + // ----------- instantX transaction scanning ----------- + + BOOST_FOREACH(const CTxIn& in, tx.vin){ + if(mapLockedInputs.count(in.prevout)){ + if(mapLockedInputs[in.prevout] != tx.GetHash()){ + return state.DoS(0, + error("AcceptableInputs : conflicts with existing transaction lock: %s", reason), + REJECT_INVALID, "tx-lock-conflict"); + } + } + } + + // Check for conflicts with in-memory transactions + { + LOCK(pool.cs); // protect pool.mapNextTx + for (unsigned int i = 0; i < tx.vin.size(); i++) + { + COutPoint outpoint = tx.vin[i].prevout; + if (pool.mapNextTx.count(outpoint)) + { + // Disable replacement feature for now + return false; + } + } + } + + + { + CCoinsView dummy; + CCoinsViewCache view(&dummy); + + CAmount nValueIn = 0; + { + LOCK(pool.cs); + CCoinsViewMemPool viewMemPool(pcoinsTip, pool); + view.SetBackend(viewMemPool); + + // do we already have it? + if (view.HaveCoins(hash)) + return false; + + // do all inputs exist? + // Note that this does not check for the presence of actual outputs (see the next check for that), + // only helps filling in pfMissingInputs (to determine missing vs spent). + for (const CTxIn txin : tx.vin) { + if (!view.HaveCoins(txin.prevout.hash)) { + if (pfMissingInputs) + *pfMissingInputs = true; + return false; + } + + // // check for invalid/fraudulent inputs + // if (!ValidOutPoint(txin.prevout, chainActive.Height())) { + // return state.Invalid(error("%s : tried to spend invalid input %s in tx %s", __func__, txin.prevout.ToString(), + // tx.GetHash().GetHex()), REJECT_INVALID, "bad-txns-invalid-inputs"); + // } + } + + // are the actual inputs available? + if (!view.HaveInputs(tx)) + return state.Invalid(error("AcceptableInputs : inputs already spent"), + REJECT_DUPLICATE, "bad-txns-inputs-spent"); + + // Bring the best block into scope + view.GetBestBlock(); + + nValueIn = view.GetValueIn(tx); + + // we have all inputs cached now, so switch back to dummy, so we don't need to keep lock on mempool + view.SetBackend(dummy); + } + + // Check for non-standard pay-to-script-hash in inputs + // for any real tx this will be checked on AcceptToMemoryPool anyway + // if (Params().RequireStandard() && !AreInputsStandard(tx, view)) + // return error("AcceptableInputs: : nonstandard transaction input"); + + // Check that the transaction doesn't have an excessive number of + // sigops, making it impossible to mine. Since the coinbase transaction + // itself can contain sigops MAX_STANDARD_TX_SIGOPS is less than + // MAX_BLOCK_SIGOPS; we still consider this an invalid rather than + // merely non-standard transaction. + unsigned int nSigOps = GetLegacySigOpCount(tx); + unsigned int nMaxSigOps = MAX_STANDARD_TX_SIGOPS; + nSigOps += GetP2SHSigOpCount(tx, view); + if (nSigOps > nMaxSigOps) + return state.DoS(0, + error("AcceptableInputs : too many sigops %s, %d > %d", + hash.ToString(), nSigOps, nMaxSigOps), + REJECT_NONSTANDARD, "bad-txns-too-many-sigops"); + + CAmount nValueOut = tx.GetValueOut(); + CAmount nFees = nValueIn-nValueOut; + double dPriority = view.GetPriority(tx, chainActive.Height()); + + // Keep track of transactions that spend a coinbase, which we re-scan + // during reorgs to ensure COINBASE_MATURITY is still met. + bool fSpendsCoinbase = false; + BOOST_FOREACH(const CTxIn &txin, tx.vin) { + const CCoins *coins = view.AccessCoins(txin.prevout.hash); + if (coins->IsCoinBase()) { + fSpendsCoinbase = true; + break; + } + } + + // Grab the branch ID we expect this transaction to commit to. We don't + // yet know if it does, but if the entry gets added to the mempool, then + // it has passed ContextualCheckInputs and therefore this is correct. + auto consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); + + CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), mempool.HasNoInputsOf(tx), fSpendsCoinbase, consensusBranchId); + unsigned int nSize = entry.GetTxSize(); + + // Don't accept it if it can't get into a block + // Accept a tx if it contains joinsplits and has at least the default fee specified by z_sendmany. + if (tx.vjoinsplit.size() > 0 && nFees >= ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE) { + // In future we will we have more accurate and dynamic computation of fees for tx with joinsplits. + } else { + CAmount txMinFee = GetMinRelayFee(tx, nSize, true); + if (fLimitFree && nFees < txMinFee) + return state.DoS(0, error("AcceptableInputs : not enough fees %s, %d < %d", + hash.ToString(), nFees, txMinFee), + REJECT_INSUFFICIENTFEE, "insufficient fee"); + + // Require that free transactions have sufficient priority to be mined in the next block. + if (GetBoolArg("-relaypriority", true) && nFees < ::minRelayTxFee.GetFee(nSize) && !AllowFree(view.GetPriority(tx, chainActive.Height() + 1))) { + return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "insufficient priority"); + } + + // Continuously rate-limit free (really, very-low-fee) transactions + // This mitigates 'penny-flooding' -- sending thousands of free transactions just to + // be annoying or make others' transactions take longer to confirm. + if (fLimitFree && nFees < ::minRelayTxFee.GetFee(nSize)) + { + static CCriticalSection csFreeLimiter; + static double dFreeCount; + static int64_t nLastTime; + int64_t nNow = GetTime(); + + LOCK(csFreeLimiter); + + // Use an exponentially decaying ~10-minute window: + dFreeCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime)); + nLastTime = nNow; + // -limitfreerelay unit is thousand-bytes-per-minute + // At default rate it would take over a month to fill 1GB + if (dFreeCount >= GetArg("-limitfreerelay", 30) * 10 * 1000) + return state.DoS(0, error("AcceptableInputs : free transaction rejected by rate limiter"), + REJECT_INSUFFICIENTFEE, "rate limited free transaction"); + LogPrint("mempool", "Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize); + dFreeCount += nSize; + } + } + + if (fRejectInsaneFee && nFees > ::minRelayTxFee.GetFee(nSize) * 10000) + return error("AcceptableInputs: : insane fees %s, %d > %d", + hash.ToString(), + nFees, ::minRelayTxFee.GetFee(nSize) * 10000); + + // Check against previous transactions + // This is done last to help prevent CPU exhaustion denial-of-service attacks. + PrecomputedTransactionData txdata(tx); + if (!ContextualCheckInputs(tx, state, view, false, STANDARD_SCRIPT_VERIFY_FLAGS, true, txdata, Params().GetConsensus(), consensusBranchId)) + return error("AcceptableInputs: : ConnectInputs failed %s", hash.ToString()); + } + + // SyncWithWallets(tx, NULL); + + return true; +} + bool GetTimestampIndex(const unsigned int &high, const unsigned int &low, const bool fActiveOnly, std::vector > &hashes) { if (!fTimestampIndex) @@ -1808,6 +2080,25 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) return nSubsidy; } +int64_t GetZeronodePayment(int nHeight, int64_t blockValue, int nZeronodeCount) +{ + int64_t ret = blockValue * 20 / 100; //default: 20% + if (IsSporkActive(SPORK_7_ZERONODE_PAYMENT_ENABLED)) { + if (IsSporkActive(SPORK_6_ZERONODE_FULL_PAYMENT_ENABLED)) { + if(nHeight >= Params().GetConsensus().nSubsidyHalvingInterval * 1) ret = blockValue * 25 / 100; + if(nHeight >= Params().GetConsensus().nSubsidyHalvingInterval * 2) ret = blockValue * 30 / 100; + if(nHeight >= Params().GetConsensus().nSubsidyHalvingInterval * 3) ret = blockValue * 35 / 100; + if(nHeight >= Params().GetConsensus().nSubsidyHalvingInterval * 4) ret = blockValue * 40 / 100; + } else { + ret = 100000; + } + } else { + ret = 0; + } + + return ret; +} + bool IsInitialBlockDownload() { const CChainParams& chainParams = Params(); @@ -1834,8 +2125,8 @@ bool IsInitialBlockDownload() return false; } -static bool fLargeWorkForkFound = false; -static bool fLargeWorkInvalidChainFound = false; +bool fLargeWorkForkFound = false; +bool fLargeWorkInvalidChainFound = false; static CBlockIndex *pindexBestForkTip = NULL; static CBlockIndex *pindexBestForkBase = NULL; @@ -3226,6 +3517,19 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock * return true; } +bool DisconnectBlocksAndReprocess(int blocks) +{ + LOCK(cs_main); + + CValidationState state; + + LogPrintf("DisconnectBlocksAndReprocess: Got command to replay %d blocks\n", blocks); + for (int i = 0; i <= blocks; i++) + DisconnectTip(state); + + return true; +} + /** * Return the tip of the chain with the most work in it, that isn't * known to be invalid (it's however far from certain to be valid). @@ -3772,6 +4076,9 @@ bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos, unsigne bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, bool fCheckPOW) { + + bool cosmosActive = NetworkUpgradeActive(chainActive.Height()+1, Params().GetConsensus(), Consensus::UPGRADE_COSMOS); + // Check block version if (block.nVersion < MIN_BLOCK_VERSION) return state.DoS(100, error("CheckBlockHeader(): block version too low"), @@ -3788,10 +4095,13 @@ bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, bool f REJECT_INVALID, "high-hash"); // Check timestamp - if (block.GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60) + if (!cosmosActive && block.GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60) return state.Invalid(error("CheckBlockHeader(): block timestamp too far in the future"), REJECT_INVALID, "time-too-new"); + if (cosmosActive && block.GetBlockTime() > GetAdjustedTime() + 10 * 60) + return state.Invalid(error("CheckBlockHeader(): block timestamp too far in the future"), + REJECT_INVALID, "time-too-new"); return true; } @@ -3840,6 +4150,58 @@ bool CheckBlock(const CBlock& block, CValidationState& state, return state.DoS(100, error("CheckBlock(): more than one coinbase"), REJECT_INVALID, "bad-cb-multiple"); + // ----------- swiftTX transaction scanning ----------- + if (IsSporkActive(SPORK_3_SWIFTTX_BLOCK_FILTERING)) { + BOOST_FOREACH (const CTransaction& tx, block.vtx) { + if (!tx.IsCoinBase()) { + //only reject blocks when it's based on complete consensus + BOOST_FOREACH (const CTxIn& in, tx.vin) { + if (mapLockedInputs.count(in.prevout)) { + if (mapLockedInputs[in.prevout] != tx.GetHash()) { + mapRejectedBlocks.insert(make_pair(block.GetHash(), GetTime())); + LogPrintf("CheckBlock() : found conflicting transaction with transaction lock %s %s\n", mapLockedInputs[in.prevout].ToString(), tx.GetHash().ToString()); + return state.DoS(0, error("CheckBlock() : found conflicting transaction with transaction lock"), + REJECT_INVALID, "conflicting-tx-ix"); + } + } + } + } + } + } else { + LogPrintf("CheckBlock() : skipping transaction locking checks\n"); + } + + + // zeronode payments / budgets + CBlockIndex* pindexPrev = chainActive.Tip(); + int nHeight = 0; + if (pindexPrev != NULL) { + if (pindexPrev->GetBlockHash() == block.hashPrevBlock) { + nHeight = pindexPrev->nHeight + 1; + } else { //out of order + BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); + if (mi != mapBlockIndex.end() && (*mi).second) + nHeight = (*mi).second->nHeight + 1; + } + + // Zero + // It is entierly possible that we don't have enough data and this could fail + // (i.e. the block could indeed be valid). Store the block for later consideration + // but issue an initial reject message. + // The case also exists that the sending peer could not have enough data to see + // that this block is invalid, so don't issue an outright ban. + if (nHeight != 0 && !IsInitialBlockDownload()) { + if (!IsBlockPayeeValid(block, nHeight)) { + mapRejectedBlocks.insert(make_pair(block.GetHash(), GetTime())); + return state.DoS(0, error("CheckBlock() : Couldn't find zeronode/budget payment"), + REJECT_INVALID, "bad-cb-payee"); + } + } else { + if (fDebug) + LogPrintf("CheckBlock(): Zeronode payment check skipped on sync - skipping IsBlockPayeeValid()\n"); + } + } + // Check transactions BOOST_FOREACH(const CTransaction& tx, block.vtx) if (!CheckTransaction(tx, state, verifier)) @@ -4106,6 +4468,12 @@ bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, bool if (!ActivateBestChain(state, pblock)) return error("%s: ActivateBestChain failed", __func__); + if (!fLiteMode) { + if (zeronodeSync.RequestedZeronodeAssets > ZERONODE_SYNC_LIST) { + zeronodePayments.ProcessBlock(GetHeight() + 10); + budget.NewBlock(); + } + } return true; } @@ -5207,6 +5575,51 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) } case MSG_BLOCK: return mapBlockIndex.count(inv.hash); + case MSG_TXLOCK_REQUEST: + return mapTxLockReq.count(inv.hash) || + mapTxLockReqRejected.count(inv.hash); + case MSG_TXLOCK_VOTE: + return mapTxLockVote.count(inv.hash); + case MSG_SPORK: + return mapSporks.count(inv.hash); + case MSG_ZERONODE_WINNER: + if (zeronodePayments.mapZeronodePayeeVotes.count(inv.hash)) { + zeronodeSync.AddedZeronodeWinner(inv.hash); + return true; + } + return false; + case MSG_BUDGET_VOTE: + if (budget.mapSeenZeronodeBudgetVotes.count(inv.hash)) { + zeronodeSync.AddedBudgetItem(inv.hash); + return true; + } + return false; + case MSG_BUDGET_PROPOSAL: + if (budget.mapSeenZeronodeBudgetProposals.count(inv.hash)) { + zeronodeSync.AddedBudgetItem(inv.hash); + return true; + } + return false; + case MSG_BUDGET_FINALIZED_VOTE: + if (budget.mapSeenFinalizedBudgetVotes.count(inv.hash)) { + zeronodeSync.AddedBudgetItem(inv.hash); + return true; + } + return false; + case MSG_BUDGET_FINALIZED: + if (budget.mapSeenFinalizedBudgets.count(inv.hash)) { + zeronodeSync.AddedBudgetItem(inv.hash); + return true; + } + return false; + case MSG_ZERONODE_ANNOUNCE: + if (znodeman.mapSeenZeronodeBroadcast.count(inv.hash)) { + zeronodeSync.AddedZeronodeList(inv.hash); + return true; + } + return false; + case MSG_ZERONODE_PING: + return znodeman.mapSeenZeronodePing.count(inv.hash); } // Don't know what it is, just say we already got one return true; @@ -5331,6 +5744,101 @@ void static ProcessGetData(CNode* pfrom) } } } + if (!pushed && inv.type == MSG_TXLOCK_VOTE) { + if (mapTxLockVote.count(inv.hash)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << mapTxLockVote[inv.hash]; + pfrom->PushMessage("txlvote", ss); + pushed = true; + } + } + if (!pushed && inv.type == MSG_TXLOCK_REQUEST) { + if (mapTxLockReq.count(inv.hash)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << mapTxLockReq[inv.hash]; + pfrom->PushMessage("ix", ss); + pushed = true; + } + } + if (!pushed && inv.type == MSG_SPORK) { + if (mapSporks.count(inv.hash)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << mapSporks[inv.hash]; + pfrom->PushMessage("spork", ss); + pushed = true; + } + } + if (!pushed && inv.type == MSG_ZERONODE_WINNER) { + if (zeronodePayments.mapZeronodePayeeVotes.count(inv.hash)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << zeronodePayments.mapZeronodePayeeVotes[inv.hash]; + pfrom->PushMessage("znw", ss); + pushed = true; + } + } + if (!pushed && inv.type == MSG_BUDGET_VOTE) { + if (budget.mapSeenZeronodeBudgetVotes.count(inv.hash)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << budget.mapSeenZeronodeBudgetVotes[inv.hash]; + pfrom->PushMessage("mvote", ss); + pushed = true; + } + } + + if (!pushed && inv.type == MSG_BUDGET_PROPOSAL) { + if (budget.mapSeenZeronodeBudgetProposals.count(inv.hash)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << budget.mapSeenZeronodeBudgetProposals[inv.hash]; + pfrom->PushMessage("zprop", ss); + pushed = true; + } + } + + if (!pushed && inv.type == MSG_BUDGET_FINALIZED_VOTE) { + if (budget.mapSeenFinalizedBudgetVotes.count(inv.hash)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << budget.mapSeenFinalizedBudgetVotes[inv.hash]; + pfrom->PushMessage("fbvote", ss); + pushed = true; + } + } + + if (!pushed && inv.type == MSG_BUDGET_FINALIZED) { + if (budget.mapSeenFinalizedBudgets.count(inv.hash)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << budget.mapSeenFinalizedBudgets[inv.hash]; + pfrom->PushMessage("fbs", ss); + pushed = true; + } + } + + if (!pushed && inv.type == MSG_ZERONODE_ANNOUNCE) { + if (znodeman.mapSeenZeronodeBroadcast.count(inv.hash)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << znodeman.mapSeenZeronodeBroadcast[inv.hash]; + pfrom->PushMessage("znb", ss); + pushed = true; + } + } + + if (!pushed && inv.type == MSG_ZERONODE_PING) { + if (znodeman.mapSeenZeronodePing.count(inv.hash)) { + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss.reserve(1000); + ss << znodeman.mapSeenZeronodePing[inv.hash]; + pfrom->PushMessage("znp", ss); + pushed = true; + } + } if (!pushed) { vNotFound.push_back(inv); @@ -6258,6 +6766,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, else { // Ignore unknown commands for extensibility + znodeman.ProcessMessage(pfrom, strCommand, vRecv); + budget.ProcessMessage(pfrom, strCommand, vRecv); + zeronodePayments.ProcessMessageZeronodePayments(pfrom, strCommand, vRecv); + ProcessMessageSwiftTX(pfrom, strCommand, vRecv); + ProcessSpork(pfrom, strCommand, vRecv); + zeronodeSync.ProcessMessage(pfrom, strCommand, vRecv); LogPrint("net", "Unknown command \"%s\" from peer=%d\n", SanitizeString(strCommand), pfrom->id); } @@ -6266,6 +6780,15 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, return true; } +// Note: whenever a protocol update is needed toggle between both implementations (comment out the formerly active one) +// so we can leave the existing clients untouched (old SPORK will stay on so they don't see even older clients). +// Those old clients won't react to the changes of the other (new) SPORK because at the time of their implementation +// it was the one which was commented out +int ActiveProtocol() +{ + return MIN_PEER_PROTO_VERSION_ENFORCEMENT; +} + // requires LOCK(cs_vRecvMsg) bool ProcessMessages(CNode* pfrom) { diff --git a/src/main.h b/src/main.h index 369b812bf3b..6ada503401a 100644 --- a/src/main.h +++ b/src/main.h @@ -41,6 +41,7 @@ class CBlockIndex; class CBlockTreeDB; +class CSporkDB; class CBloomFilter; class CInv; class CScriptCheck; @@ -139,12 +140,17 @@ extern bool fTxIndex; extern bool fIsBareMultisigStd; extern bool fCheckBlockIndex; extern bool fCheckpointsEnabled; + +extern bool fLargeWorkForkFound; +extern bool fLargeWorkInvalidChainFound; + // TODO: remove this flag by structuring our code such that // it is unneeded for testing extern bool fCoinbaseEnforcedProtectionEnabled; extern size_t nCoinCacheUsage; extern CFeeRate minRelayTxFee; extern bool fAlerts; +extern std::map mapRejectedBlocks; extern int64_t nMaxTipAge; /** Best header we've seen so far (used for getheaders queries' starting points). */ @@ -226,6 +232,9 @@ bool IsInitialBlockDownload(); std::string GetWarnings(const std::string& strFor); /** Retrieve a transaction (from memory pool, or from disk, if possible) */ bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock, bool fAllowSlow = false); + +bool DisconnectBlocksAndReprocess(int blocks); + /** Find the best known block, and make it the tip of the block chain */ bool ActivateBestChain(CValidationState &state, CBlock *pblock = NULL); CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams); @@ -246,7 +255,7 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams); * @param[out] setFilesToPrune The set of file indices that can be unlinked will be returned */ void FindFilesToPrune(std::set& setFilesToPrune); - +int64_t GetZeronodePayment(int nHeight, int64_t blockValue, int nZeronodeCount = 0); /** * Actually unlink the specified files */ @@ -262,11 +271,16 @@ void Misbehaving(NodeId nodeid, int howmuch); void FlushStateToDisk(); /** Prune block files and flush state to disk. */ void PruneAndFlush(); - +/** See whether the protocol update is enforced for connected nodes */ +int ActiveProtocol(); /** (try to) add transaction to memory pool **/ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree, bool* pfMissingInputs, bool fRejectAbsurdFee=false); - +bool AcceptableInputs(CTxMemPool& pool, CValidationState& state, const CTransaction& tx, bool fLimitFree, bool* pfMissingInputs, bool fRejectInsaneFee = false); +int GetInputAge(CTxIn& vin); +int GetInputAgeIX(uint256 nTXHash, CTxIn& vin); +bool GetCoinAge(const CTransaction& tx, unsigned int nTxTime, uint64_t& nCoinAge); +int GetIXConfirmations(uint256 nTXHash); struct CNodeStateStats { int nMisbehavior; @@ -799,6 +813,9 @@ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex); * of problems. Note that in any case, coins may be modified. */ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool* pfClean = NULL); +/** Reprocess a number of blocks to try and get on the correct chain again **/ +bool DisconnectBlocksAndReprocess(int blocks); + /** Apply the effects of this block (with given index) on the UTXO set represented by coins */ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& coins, bool fJustCheck = false); @@ -917,6 +934,8 @@ extern CCoinsViewCache *pcoinsTip; /** Global variable that points to the active block tree (protected by cs_main) */ extern CBlockTreeDB *pblocktree; +/** Global variable that points to the spork database (protected by cs_main) */ +extern CSporkDB* pSporkDB; /** * Return the spend height, which is one more than the inputs.GetBestBlock(). * While checking, GetBestBlock() refers to the parent block. (protected by cs_main) diff --git a/src/miner.cpp b/src/miner.cpp index d501ab9e7ef..9cfe1acc918 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -20,6 +20,7 @@ #include "key_io.h" #include "main.h" #include "metrics.h" +#include "zeronode/zeronode-sync.h" #include "net.h" #include "pow.h" #include "primitives/transaction.h" @@ -32,6 +33,12 @@ #include "sodium.h" +#ifdef ENABLE_WALLET +#include "wallet/wallet.h" +#endif +#include "zeronode/payments.h" +#include "zeronode/spork.h" + #include #include #ifdef ENABLE_MINING @@ -402,22 +409,17 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) txNew.vin[0].prevout.SetNull(); txNew.vout.resize(1); txNew.vout[0].scriptPubKey = scriptPubKeyIn; - txNew.vout[0].nValue = GetBlockSubsidy(nHeight, chainparams.GetConsensus()); + // Set to 0 so expiry height does not apply to coinbase txs txNew.nExpiryHeight = 0; - if ((nHeight >= chainparams.GetConsensus().nFeeStartBlockHeight) && (nHeight <= chainparams.GetConsensus().GetLastFoundersRewardBlockHeight())) { - // Founders reward is 20% of the block subsidy - auto vFoundersReward = txNew.vout[0].nValue * 0.075; - // Take some reward away from us - txNew.vout[0].nValue -= vFoundersReward; + // Zeronode and general budget payments + FillBlockPayee(txNew, nFees, pblock->txoutFounders, pblock->txoutZeronode); - // And give it to the founders - txNew.vout.push_back(CTxOut(vFoundersReward, chainparams.GetFoundersRewardScriptAtHeight(nHeight))); - } + // Make payee + pblock->payee = txNew.vout[txNew.vout.size() - 1].scriptPubKey; // Add fees - txNew.vout[0].nValue += nFees; txNew.vin[0].scriptSig = CScript() << nHeight << OP_0; pblock->vtx[0] = txNew; diff --git a/src/net.cpp b/src/net.cpp index f0f7dc8ab60..1ca38e9d11a 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -353,16 +353,16 @@ CNode* FindNode(const CService& addr) return NULL; } -CNode* ConnectNode(CAddress addrConnect, const char *pszDest) +CNode* ConnectNode(CAddress addrConnect, const char* pszDest, bool obfuScationMaster) { if (pszDest == NULL) { - if (IsLocal(addrConnect)) + if (IsLocal(addrConnect) && !obfuScationMaster) return NULL; // Look for an existing connection CNode* pnode = FindNode((CService)addrConnect); - if (pnode) - { + if (pnode) { + pnode->fObfuScationMaster = obfuScationMaster; pnode->AddRef(); return pnode; } @@ -397,6 +397,7 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest) } pnode->nTimeConnected = GetTime(); + if (obfuScationMaster) pnode->fObfuScationMaster = true; return pnode; } else if (!proxyConnectionFailed) { @@ -731,7 +732,6 @@ class CNodeRef { { if (this != &other) { LOCK(cs_vNodes); - _pnode->Release(); _pnode = other._pnode; _pnode->AddRef(); @@ -1892,6 +1892,30 @@ void RelayTransaction(const CTransaction& tx, const CDataStream& ss) } } +void RelayTransactionLockReq(const CTransaction& tx, bool relayToAll) +{ + CInv inv(MSG_TXLOCK_REQUEST, tx.GetHash()); + + //broadcast the new lock + LOCK(cs_vNodes); + BOOST_FOREACH (CNode* pnode, vNodes) { + if (!relayToAll && !pnode->fRelayTxes) + continue; + + pnode->PushMessage("ix", tx); + } +} + +void RelayInv(CInv& inv) +{ + LOCK(cs_vNodes); + BOOST_FOREACH (CNode* pnode, vNodes){ + if((pnode->nServices==NODE_BLOOM_WITHOUT_MN) && inv.IsZeroNodeType())continue; + if (pnode->nVersion >= ActiveProtocol()) + pnode->PushInventory(inv); + } +} + void CNode::RecordBytesRecv(uint64_t bytes) { LOCK(cs_totalBytesRecv); diff --git a/src/net.h b/src/net.h index b5e365b356b..a159ae77e38 100644 --- a/src/net.h +++ b/src/net.h @@ -71,7 +71,7 @@ CNode* FindNode(const CNetAddr& ip); CNode* FindNode(const CSubNet& subNet); CNode* FindNode(const std::string& addrName); CNode* FindNode(const CService& ip); -CNode* ConnectNode(CAddress addrConnect, const char *pszDest = NULL); +CNode* ConnectNode(CAddress addrConnect, const char *pszDest = NULL, bool obfuScationMaster=false); bool OpenNetworkConnection(const CAddress& addrConnect, CSemaphoreGrant *grantOutbound = NULL, const char *strDest = NULL, bool fOneShot = false); unsigned short GetListenPort(); bool BindListenPort(const CService &bindAddr, std::string& strError, bool fWhitelisted = false); @@ -281,6 +281,12 @@ class CNode // b) the peer may tell us in its version message that we should not relay tx invs // until it has initialized its bloom filter. bool fRelayTxes; + // Should be 'true' only if we connected to this node to actually mix funds. + // In this case node will be released automatically via CZeronodeMan::ProcessZeronodeConnections(). + // Connecting to verify connectability/status or connecting for sending/relaying single message + // (even if it's relative to mixing e.g. for blinding) should NOT set this to 'true'. + // For such cases node should be released manually (preferably right after corresponding code). + bool fObfuScationMaster; bool fSentAddr; CSemaphoreGrant grantOutbound; CCriticalSection cs_filter; @@ -294,6 +300,8 @@ class CNode static std::map setBanned; static CCriticalSection cs_setBanned; + std::vector vecRequestsFulfilled; //keep track of what client has asked for + // Whitelisted ranges. Any node connecting from these is automatically // whitelisted (as well as those connecting to whitelisted binds). static std::vector vWhitelistedRange; @@ -352,7 +360,7 @@ class CNode int GetRefCount() { - assert(nRefCount >= 0); + //assert(nRefCount >= 0); return nRefCount; } @@ -598,6 +606,58 @@ class CNode } } + template + void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5, const T6& a6, const T7& a7, const T8& a8, const T9& a9, const T10& a10, const T11& a11) + { + try { + BeginMessage(pszCommand); + ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9 << a10 << a11; + EndMessage(); + } catch (...) { + AbortMessage(); + throw; + } + } + + template + void PushMessage(const char* pszCommand, const T1& a1, const T2& a2, const T3& a3, const T4& a4, const T5& a5, const T6& a6, const T7& a7, const T8& a8, const T9& a9, const T10& a10, const T11& a11, const T12& a12) + { + try { + BeginMessage(pszCommand); + ssSend << a1 << a2 << a3 << a4 << a5 << a6 << a7 << a8 << a9 << a10 << a11 << a12; + EndMessage(); + } catch (...) { + AbortMessage(); + throw; + } + } + + bool HasFulfilledRequest(std::string strRequest) + { + BOOST_FOREACH (std::string& type, vecRequestsFulfilled) { + if (type == strRequest) return true; + } + return false; + } + + void ClearFulfilledRequest(std::string strRequest) + { + std::vector::iterator it = vecRequestsFulfilled.begin(); + while (it != vecRequestsFulfilled.end()) { + if ((*it) == strRequest) { + vecRequestsFulfilled.erase(it); + return; + } + ++it; + } + } + + void FulfilledRequest(std::string strRequest) + { + if (HasFulfilledRequest(strRequest)) return; + vecRequestsFulfilled.push_back(strRequest); + } + void CloseSocketDisconnect(); // Denial-of-service detection/prevention @@ -641,6 +701,8 @@ class CNode class CTransaction; void RelayTransaction(const CTransaction& tx); void RelayTransaction(const CTransaction& tx, const CDataStream& ss); +void RelayTransactionLockReq(const CTransaction& tx, bool relayToAll = false); +void RelayInv(CInv& inv); /** Access to the (IP) address database (peers.dat) */ class CAddrDB diff --git a/src/primitives/block.h b/src/primitives/block.h index 489d54711ef..750ae37749f 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -84,8 +84,13 @@ class CBlock : public CBlockHeader std::vector vtx; // memory only + mutable CScript payee; mutable std::vector vMerkleTree; + // memory only for blocktemplate - Zeronode updates + mutable CTxOut txoutFounders; + mutable CTxOut txoutZeronode; + CBlock() { SetNull(); @@ -109,7 +114,10 @@ class CBlock : public CBlockHeader { CBlockHeader::SetNull(); vtx.clear(); + payee = CScript(); vMerkleTree.clear(); + txoutFounders = CTxOut(); + txoutZeronode = CTxOut(); } CBlockHeader GetBlockHeader() const diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index e6c88003af6..819cbd76ed4 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -144,6 +144,11 @@ uint256 JSDescription::h_sig(ZCJoinSplit& params, const uint256& joinSplitPubKey return params.h_sig(randomSeed, nullifiers, joinSplitPubKey); } +std::string COutPoint::ToStringShort() const +{ + return strprintf("%s-%u", hash.ToString().substr(0,64), n); +} + std::string COutPoint::ToString() const { return strprintf("COutPoint(%s, %u)", hash.ToString().substr(0,10), n); @@ -154,6 +159,11 @@ std::string SaplingOutPoint::ToString() const return strprintf("SaplingOutPoint(%s, %u)", hash.ToString().substr(0, 10), n); } +uint256 COutPoint::GetHash() +{ + return Hash(BEGIN(hash), END(hash), BEGIN(n), END(n)); +} + CTxIn::CTxIn(COutPoint prevoutIn, CScript scriptSigIn, uint32_t nSequenceIn) { prevout = prevoutIn; @@ -214,6 +224,47 @@ uint256 CMutableTransaction::GetHash() const return SerializeHash(*this); } +std::string CMutableTransaction::ToString() const +{ + std::string str; + if (!fOverwintered) { + str += strprintf("CTransaction(hash=%s, ver=%d, vin.size=%u, vout.size=%u, nLockTime=%u)\n", + GetHash().ToString().substr(0,10), + nVersion, + vin.size(), + vout.size(), + nLockTime); + } else if (nVersion >= SAPLING_MIN_TX_VERSION) { + str += strprintf("CTransaction(hash=%s, ver=%d, fOverwintered=%d, nVersionGroupId=%08x, vin.size=%u, vout.size=%u, nLockTime=%u, nExpiryHeight=%u, valueBalance=%u, vShieldedSpend.size=%u, vShieldedOutput.size=%u)\n", + GetHash().ToString().substr(0,10), + nVersion, + fOverwintered, + nVersionGroupId, + vin.size(), + vout.size(), + nLockTime, + nExpiryHeight, + valueBalance, + vShieldedSpend.size(), + vShieldedOutput.size()); + } else if (nVersion >= 3) { + str += strprintf("CTransaction(hash=%s, ver=%d, fOverwintered=%d, nVersionGroupId=%08x, vin.size=%u, vout.size=%u, nLockTime=%u, nExpiryHeight=%u)\n", + GetHash().ToString().substr(0,10), + nVersion, + fOverwintered, + nVersionGroupId, + vin.size(), + vout.size(), + nLockTime, + nExpiryHeight); + } + for (unsigned int i = 0; i < vin.size(); i++) + str += " " + vin[i].ToString() + "\n"; + for (unsigned int i = 0; i < vout.size(); i++) + str += " " + vout[i].ToString() + "\n"; + return str; +} + void CTransaction::UpdateHash() const { *const_cast(&hash) = SerializeHash(*this); diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index e39a24fa7a1..940388c32d8 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -351,7 +351,9 @@ class COutPoint : public BaseOutPoint public: COutPoint() : BaseOutPoint() {}; COutPoint(uint256 hashIn, uint32_t nIn) : BaseOutPoint(hashIn, nIn) {}; + std::string ToStringShort() const; std::string ToString() const; + uint256 GetHash(); }; /** An outpoint - a combination of a transaction hash and an index n into its sapling @@ -360,7 +362,7 @@ class SaplingOutPoint : public BaseOutPoint { public: SaplingOutPoint() : BaseOutPoint() {}; - SaplingOutPoint(uint256 hashIn, uint32_t nIn) : BaseOutPoint(hashIn, nIn) {}; + SaplingOutPoint(uint256 hashIn, uint32_t nIn) : BaseOutPoint(hashIn, nIn) {}; std::string ToString() const; }; @@ -374,6 +376,7 @@ class CTxIn COutPoint prevout; CScript scriptSig; uint32_t nSequence; + CScript prevPubKey; CTxIn() { @@ -778,6 +781,18 @@ struct CMutableTransaction * fly, as opposed to GetHash() in CTransaction, which uses a cached result. */ uint256 GetHash() const; + + std::string ToString() const; + + friend bool operator==(const CMutableTransaction& a, const CMutableTransaction& b) + { + return a.GetHash() == b.GetHash(); + } + + friend bool operator!=(const CMutableTransaction& a, const CMutableTransaction& b) + { + return !(a == b); + } }; #endif // BITCOIN_PRIMITIVES_TRANSACTION_H diff --git a/src/protocol.cpp b/src/protocol.cpp index dd855aa33aa..e0c5b2f1fc0 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -17,7 +17,19 @@ static const char* ppszTypeName[] = "ERROR", "tx", "block", - "filtered block" + "filtered block", + "tx lock request", + "tx lock vote", + "spork", + "zn winner", + "zn scan error", + "zn budget vote", + "zn budget proposal", + "zn budget finalized", + "zn budget finalized vote", + "zn quorum", + "zn announce", + "zn ping" }; CMessageHeader::CMessageHeader(const MessageStartChars& pchMessageStartIn) @@ -129,6 +141,10 @@ bool CInv::IsKnownType() const return (type >= 1 && type < (int)ARRAYLEN(ppszTypeName)); } +bool CInv::IsZeroNodeType() const{ + return (type >= 6); +} + const char* CInv::GetCommand() const { if (!IsKnownType()) diff --git a/src/protocol.h b/src/protocol.h index d908191ccbc..9e7f321e247 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -76,6 +76,10 @@ enum { // but no longer do as of protocol version 170004 (= NO_BLOOM_VERSION) NODE_BLOOM = (1 << 2), + // NODE_BLOOM_WITHOUT_MN means the node has the same features as NODE_BLOOM with the only difference + // that the node doens't want to receive master nodes messages. (the 1<<3 was not picked as constant because on bitcoin 0.14 is witness and we want that update here ) + NODE_BLOOM_WITHOUT_MN = (1 << 4), + // Bits 24-31 are reserved for temporary experiments. Just pick a bit that // isn't getting used, or one not being used much, and notify the // bitcoin-development mailing list. Remember that service bits are just @@ -139,6 +143,7 @@ class CInv friend bool operator<(const CInv& a, const CInv& b); bool IsKnownType() const; + bool IsZeroNodeType() const; const char* GetCommand() const; std::string ToString() const; @@ -154,6 +159,18 @@ enum { // Nodes may always request a MSG_FILTERED_BLOCK in a getdata, however, // MSG_FILTERED_BLOCK should not appear in any invs except as a part of getdata. MSG_FILTERED_BLOCK, + MSG_TXLOCK_REQUEST, + MSG_TXLOCK_VOTE, + MSG_SPORK, + MSG_ZERONODE_WINNER, + MSG_ZERONODE_SCANNING_ERROR, + MSG_BUDGET_VOTE, + MSG_BUDGET_PROPOSAL, + MSG_BUDGET_FINALIZED, + MSG_BUDGET_FINALIZED_VOTE, + MSG_ZERONODE_QUORUM, + MSG_ZERONODE_ANNOUNCE, + MSG_ZERONODE_PING }; #endif // BITCOIN_PROTOCOL_H diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index cc035cf74fe..b77cfdbe57a 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -24,6 +24,7 @@ class CRPCConvertParam static const CRPCConvertParam vRPCConvertParams[] = { { "getalldata", 0}, + { "getalldata", 1}, { "stop", 0 }, { "setmocktime", 0 }, { "getaddednodeinfo", 0 }, @@ -140,7 +141,9 @@ static const CRPCConvertParam vRPCConvertParams[] = { "z_importkey", 2 }, { "z_importviewingkey", 2 }, { "z_getpaymentdisclosure", 1}, - { "z_getpaymentdisclosure", 2} + { "z_getpaymentdisclosure", 2}, + { "spork", 1}, + { "spork", 2}, }; class CRPCConvertTable diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index abbd67cd136..45502541f09 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -12,6 +12,7 @@ #include "crypto/equihash.h" #endif #include "init.h" +#include "key_io.h" #include "main.h" #include "metrics.h" #include "miner.h" @@ -21,6 +22,7 @@ #include "txmempool.h" #include "util.h" #include "validationinterface.h" +#include "zeronode/spork.h" #include @@ -459,6 +461,20 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp) " \"curtime\" : ttt, (numeric) current timestamp in seconds since epoch (Jan 1 1970 GMT)\n" " \"bits\" : \"xxx\", (string) compressed target of next block\n" " \"height\" : n (numeric) The height of the next block\n" + " \"votes\" : [\n (array) show vote candidates\n" + " { ... } (json object) vote candidate\n" + " ,...\n" + " ],\n" + " \"coinbase_required_outputs\" : [\n (array) required coinbase transactions\n" + " {\n" + " \"payee\" : \"xxxx\", (string) required payee for the next block\n" + " \"payee_amount\" : \"xxxx\", (numeric) required amount to pay\n" + " \"type\" : \"xxxx\" (string) payee type, devfee, utilitynode, etc...\n" + " }\n" + " \"payee\" : \"xxx\", (string) required zeronode payee for the next block\n" + " \"payee_amount\" : n, (numeric) required amount to pay\n" + " \"zeronode_payments\" : true|false, (boolean) true, if zeronode payments are enabled\n" + " \"enforce_zeronode_payments\" : true|false (boolean) true, if zeronode payments are enforced\n" "}\n" "\nExamples:\n" @@ -532,7 +548,7 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); if (vNodes.empty()) - throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Zcash is not connected!"); + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "ZERO is not connected!"); if (IsInitialBlockDownload()) throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "ZERO is downloading blocks..."); @@ -663,9 +679,9 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp) if (tx.IsCoinBase()) { // Show founders' reward if it is required - if (pblock->vtx[0].vout.size() > 1) { + if (pblock->txoutFounders != CTxOut()) { // Correct this if GetBlockTemplate changes the order - entry.push_back(Pair("foundersreward", (int64_t)tx.vout[1].nValue)); + entry.push_back(Pair("foundersreward", (int64_t)pblock->txoutFounders.nValue)); } entry.push_back(Pair("required", true)); txCoinbase = entry; @@ -687,6 +703,7 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp) aMutable.push_back("prevblock"); } + UniValue aVotes(UniValue::VARR); UniValue result(UniValue::VOBJ); result.push_back(Pair("capabilities", aCaps)); result.push_back(Pair("version", pblock->nVersion)); @@ -710,6 +727,43 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp) result.push_back(Pair("curtime", pblock->GetBlockTime())); result.push_back(Pair("bits", strprintf("%08x", pblock->nBits))); result.push_back(Pair("height", (int64_t)(pindexPrev->nHeight+1))); + result.push_back(Pair("votes", aVotes)); + + //List of all required coinbase transactions + UniValue requiredCoinbaseArr(UniValue::VARR); + if (pblock->txoutFounders != CTxOut()) { + UniValue rqCoinbase(UniValue::VOBJ); + CTxDestination dest; + ExtractDestination(pblock->txoutFounders.scriptPubKey, dest); + rqCoinbase.push_back(Pair("payee", EncodeDestination(dest))); + rqCoinbase.push_back(Pair("script", HexStr(pblock->txoutFounders.scriptPubKey.begin(), pblock->txoutFounders.scriptPubKey.end()))); + rqCoinbase.push_back(Pair("amount", pblock->txoutFounders.nValue)); + rqCoinbase.push_back(Pair("type", "foundersreward")); + rqCoinbase.push_back(Pair("enforced", true)); + requiredCoinbaseArr.push_back(rqCoinbase); + } + + if(pblock->txoutZeronode != CTxOut()) { + UniValue rqCoinbase(UniValue::VOBJ); + CTxDestination dest; + ExtractDestination(pblock->txoutZeronode.scriptPubKey, dest); + rqCoinbase.push_back(Pair("payee", EncodeDestination(dest))); + rqCoinbase.push_back(Pair("script", HexStr(pblock->txoutZeronode.scriptPubKey.begin(), pblock->txoutZeronode.scriptPubKey.end()))); + rqCoinbase.push_back(Pair("amount", pblock->txoutZeronode.nValue)); + rqCoinbase.push_back(Pair("type", "zeronode")); + rqCoinbase.push_back(Pair("enforced", IsSporkActive(SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT))); + requiredCoinbaseArr.push_back(rqCoinbase); + // Legacy Masternode blocktemplate + result.push_back(Pair("payee", EncodeDestination(dest))); + result.push_back(Pair("payee_amount", pblock->txoutZeronode.nValue)); + } else { + // Legacy Masternode blocktemplate + result.push_back(Pair("payee", "")); + result.push_back(Pair("payee_amount", "")); + } + result.push_back(Pair("masternode_payments", IsSporkActive(SPORK_7_ZERONODE_PAYMENT_ENABLED))); + result.push_back(Pair("enforce_masternode_payments", IsSporkActive(SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT))); + result.push_back(Pair("coinbase_required_outputs",requiredCoinbaseArr)); return result; } diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 7ab678f6769..a918bcef9d6 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -90,8 +90,7 @@ UniValue getalldata(const UniValue& params, bool fHelp) int nMinDepth = 1; CAmount nBalance = getBalanceTaddr("", nMinDepth, true); CAmount nPrivateBalance = getBalanceZaddr("", nMinDepth, true); - //CAmount nLockedCoin = pwalletMain->GetLockedCoins(); - CAmount nLockedCoin = 0; + CAmount nLockedCoin = pwalletMain->GetLockedCoins(); CAmount nTotalBalance = nBalance + nPrivateBalance + nLockedCoin; @@ -115,73 +114,99 @@ UniValue getalldata(const UniValue& params, bool fHelp) if (params.size() > 0 && (params[0].get_int() == 1 || params[0].get_int() == 0)) { - for (const std::pair& item : pwalletMain->mapAddressBook) { + BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, pwalletMain->mapAddressBook) + { UniValue addr(UniValue::VOBJ); const CTxDestination& dest = item.first; + isminetype mine = pwalletMain ? IsMine(*pwalletMain, dest) : ISMINE_NO; - const std::string& strAccount = item.second.name; + + const string& strName = item.second.name; nBalance = getBalanceTaddr(EncodeDestination(dest), nMinDepth, false); + addr.push_back(Pair("amount", ValueFromAmount(nBalance))); addr.push_back(Pair("ismine", (mine & ISMINE_SPENDABLE) ? true : false)); addrlist.push_back(Pair(EncodeDestination(dest), addr)); } //address grouping - LOCK2(cs_main, pwalletMain->cs_wallet); - map balances = pwalletMain->GetAddressBalances(); - BOOST_FOREACH(set grouping, pwalletMain->GetAddressGroupings()) { - BOOST_FOREACH(CTxDestination address, grouping) + LOCK2(cs_main, pwalletMain->cs_wallet); + + map balances = pwalletMain->GetAddressBalances(); + BOOST_FOREACH(set grouping, pwalletMain->GetAddressGroupings()) { - UniValue addr(UniValue::VOBJ); - const string& strName = EncodeDestination(address); - std::cout << "Address " << strName << "\n"; - if(addrlist.exists(strName)) - continue; - isminetype mine = pwalletMain ? IsMine(*pwalletMain, address) : ISMINE_NO; - nBalance = getBalanceTaddr(strName, nMinDepth, false); - addr.push_back(Pair("amount", ValueFromAmount(nBalance))); - addr.push_back(Pair("ismine", (mine & ISMINE_SPENDABLE) ? true : false)); - addrlist.push_back(Pair(strName, addr)); + BOOST_FOREACH(CTxDestination address, grouping) + { + UniValue addr(UniValue::VOBJ); + const string& strName = EncodeDestination(address); + if(addrlist.exists(strName)) + continue; + isminetype mine = pwalletMain ? IsMine(*pwalletMain, address) : ISMINE_NO; + + nBalance = getBalanceTaddr(strName, nMinDepth, false); + + addr.push_back(Pair("amount", ValueFromAmount(nBalance))); + addr.push_back(Pair("ismine", (mine & ISMINE_SPENDABLE) ? true : false)); + addrlist.push_back(Pair(strName, addr)); + } } } - //get all z address - Sprout - std::set addressSprout; - pwalletMain->GetSproutPaymentAddresses(addressSprout); - for (auto addr : addressSprout ) { - if (pwalletMain->HaveSproutSpendingKey(addr)) { - UniValue address(UniValue::VOBJ); - const string& strName = EncodePaymentAddress(addr); - nBalance = getBalanceZaddr(strName, nMinDepth, false); - address.push_back(Pair("amount", ValueFromAmount(nBalance))); - address.push_back(Pair("ismine", true)); - addrlist.push_back(Pair(strName, address)); + //get all z address + { + std::set addresses; + pwalletMain->GetSproutPaymentAddresses(addresses); + for (auto addr : addresses) { + if (pwalletMain->HaveSproutSpendingKey(addr)) { + UniValue address(UniValue::VOBJ); + const string& strName = EncodePaymentAddress(addr); + nBalance = getBalanceZaddr(strName, nMinDepth, false); + address.push_back(Pair("amount", ValueFromAmount(nBalance))); + address.push_back(Pair("ismine", true)); + addrlist.push_back(Pair(strName, address)); + } + else + { + UniValue address(UniValue::VOBJ); + const string& strName = EncodePaymentAddress(addr); + nBalance = getBalanceZaddr(strName, nMinDepth, false); + address.push_back(Pair("amount", ValueFromAmount(nBalance))); + address.push_back(Pair("ismine", false)); + addrlist.push_back(Pair(strName, address)); + } } } - - //get all z address - Sapling - std::set addressSapling; - pwalletMain->GetSaplingPaymentAddresses(addressSapling); - for (auto addr : addressSapling ) { - libzcash::SaplingIncomingViewingKey ivk; - libzcash::SaplingFullViewingKey fvk; - if (pwalletMain->GetSaplingIncomingViewingKey(addr, ivk) && - pwalletMain->GetSaplingFullViewingKey(ivk, fvk) && - pwalletMain->HaveSaplingSpendingKey(fvk)) { - UniValue address(UniValue::VOBJ); - const string& strName = EncodePaymentAddress(addr); - nBalance = getBalanceZaddr(strName, nMinDepth, false); - address.push_back(Pair("amount", ValueFromAmount(nBalance))); - address.push_back(Pair("ismine", true)); - addrlist.push_back(Pair(strName, address)); + { + std::set addresses; + pwalletMain->GetSaplingPaymentAddresses(addresses); + libzcash::SaplingIncomingViewingKey ivk; + libzcash::SaplingFullViewingKey fvk; + for (auto addr : addresses) { + if (pwalletMain->GetSaplingIncomingViewingKey(addr, ivk) && + pwalletMain->GetSaplingFullViewingKey(ivk, fvk)) { + if(pwalletMain->HaveSaplingSpendingKey(fvk)) { + UniValue address(UniValue::VOBJ); + const string& strName = EncodePaymentAddress(addr); + nBalance = getBalanceZaddr(strName, nMinDepth, false); + address.push_back(Pair("amount", ValueFromAmount(nBalance))); + address.push_back(Pair("ismine", true)); + addrlist.push_back(Pair(strName, address)); + } + else + { + UniValue address(UniValue::VOBJ); + const string& strName = EncodePaymentAddress(addr); + nBalance = getBalanceZaddr(strName, nMinDepth, false); + address.push_back(Pair("amount", ValueFromAmount(nBalance))); + address.push_back(Pair("ismine", false)); + addrlist.push_back(Pair(strName, address)); + } + } } } - } - - addressbalance.push_back(addrlist); returnObj.push_back(Pair("addressbalance", addressbalance)); @@ -211,6 +236,14 @@ UniValue getalldata(const UniValue& params, bool fHelp) { day = 30; } + else if(params[1].get_int() == 4) + { + day = 90; + } + else if(params[1].get_int() == 5) + { + day = 365; + } } std::list acentries; @@ -225,7 +258,11 @@ UniValue getalldata(const UniValue& params, bool fHelp) CAccountingEntry *const pacentry = (*it).second.second; if (pacentry != 0) AcentryToJSON(*pacentry, strAccount, trans); - if (pwtx->nTimeReceived <= (t - (day * 60 * 60 * 24))) break; + int confirms = pwtx->GetDepthInMainChain(); + if(confirms > 0) + { + if (mapBlockIndex[pwtx->hashBlock]->GetBlockTime() <= (t - (day * 60 * 60 * 24)) && (int)trans.size() >= nCount) break; + } } vector arrTmp = trans.getValues(); diff --git a/src/rpc/register.h b/src/rpc/register.h index 01aa58a25d8..6aa1d1e23d5 100644 --- a/src/rpc/register.h +++ b/src/rpc/register.h @@ -19,6 +19,12 @@ void RegisterMiscRPCCommands(CRPCTable &tableRPC); void RegisterMiningRPCCommands(CRPCTable &tableRPC); /** Register raw transaction RPC commands */ void RegisterRawTransactionRPCCommands(CRPCTable &tableRPC); +/**Register Zeronode Commands */ +void RegisterZeronodeRPCCommands(CRPCTable &tableRPC); +/**Register Budget Commands */ +void RegisterBudgetRPCCommands(CRPCTable &tableRPC); +/** Register Spork RPC commands */ +void RegisterSporkRPCCommands(CRPCTable &tableRPC); static inline void RegisterAllCoreRPCCommands(CRPCTable &tableRPC) { @@ -27,6 +33,9 @@ static inline void RegisterAllCoreRPCCommands(CRPCTable &tableRPC) RegisterMiscRPCCommands(tableRPC); RegisterMiningRPCCommands(tableRPC); RegisterRawTransactionRPCCommands(tableRPC); + RegisterZeronodeRPCCommands(tableRPC); + //RegisterBudgetRPCCommands(tableRPC); //Disabled for now + RegisterSporkRPCCommands(tableRPC); } #endif diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 0dd1db51d3c..638648259c0 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -245,11 +245,11 @@ UniValue stop(const UniValue& params, bool fHelp) if (fHelp || params.size() > 1) throw runtime_error( "stop\n" - "\nStop Zcash server."); + "\nStop Zero server."); // Event loop will exit after current HTTP requests have been handled, so // this reply will get back to the client. StartShutdown(); - return "Zcash server stopping"; + return "Zero server stopping"; } /** diff --git a/src/rpc/spork.cpp b/src/rpc/spork.cpp new file mode 100644 index 00000000000..05d5ac442ff --- /dev/null +++ b/src/rpc/spork.cpp @@ -0,0 +1,120 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2019 The Zero developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "init.h" +#include "key_io.h" +#include "main.h" +#include "rpc/server.h" +#include "zeronode/spork.h" + +#include + +#include + +using namespace std; + +UniValue spork(const UniValue& params, bool fHelp) +{ + if (params.size() == 1 && params[0].get_str() == "show") { + UniValue ret(UniValue::VOBJ); + for (int nSporkID = SPORK_START; nSporkID <= SPORK_END; nSporkID++) { + if (sporkManager.GetSporkNameByID(nSporkID) != "Unknown") + ret.push_back(Pair(sporkManager.GetSporkNameByID(nSporkID), GetSporkValue(nSporkID))); + } + return ret; + } else if (params.size() == 1 && params[0].get_str() == "active") { + UniValue ret(UniValue::VOBJ); + for (int nSporkID = SPORK_START; nSporkID <= SPORK_END; nSporkID++) { + if (sporkManager.GetSporkNameByID(nSporkID) != "Unknown") + ret.push_back(Pair(sporkManager.GetSporkNameByID(nSporkID), IsSporkActive(nSporkID))); + } + return ret; + } else if (params.size() == 2) { + int nSporkID = sporkManager.GetSporkIDByName(params[0].get_str()); + if (nSporkID == -1) { + return "Invalid spork name"; + } + + // SPORK VALUE + int64_t nValue = params[1].get_int64(); + + //broadcast new spork + if (sporkManager.UpdateSpork(nSporkID, nValue)) { + return "success"; + } else { + return "failure"; + } + } + + throw runtime_error( + "spork \"name\" ( value )\n" + "\nReturn spork values or their active state.\n" + + "\nArguments:\n" + "1. \"name\" (string, required) \"show\" to show values, \"active\" to show active state.\n" + " When set up as a spork signer, the name of the spork can be used to update it's value.\n" + "2. value (numeric, required when updating a spork) The new value for the spork.\n" + + "\nResult (show):\n" + "{\n" + " \"spork_name\": nnn (key/value) Key is the spork name, value is it's current value.\n" + " ,...\n" + "}\n" + + "\nResult (active):\n" + "{\n" + " \"spork_name\": true|false (key/value) Key is the spork name, value is a boolean for it's active state.\n" + " ,...\n" + "}\n" + + "\nResult (name):\n" + " \"success|failure\" (string) Wither or not the update succeeded.\n" + + "\nExamples:\n" + + HelpExampleCli("spork", "show") + HelpExampleRpc("spork", "show")); +} + +UniValue createsporkkeys(const UniValue& params, bool fHelp) +{ + if (fHelp || (params.size() != 0)) + throw runtime_error( + "createsporkkeys\n" + "\nCreate a set of private and public keys used for sporks\n" + + "\nResult:\n" + "\"pubkey\" (string) Spork public key\n" + "\"privkey\" (string) Spork private key\n" + + "\nExamples:\n" + + HelpExampleCli("createsporkkeys", "") + HelpExampleRpc("createsporkkeys", "")); + + CKey secret; + secret.MakeNewKey(false); + + CPubKey pubKey = secret.GetPubKey(); + + std::string str; + for (int i = 0; i < pubKey.size(); i++) { + str += pubKey[i]; + } + + UniValue ret(UniValue::VOBJ); + ret.push_back(Pair("pubkey", HexStr(str))); + ret.push_back(Pair("privkey", EncodeSecret(secret))); + return ret; +} + +static const CRPCCommand commands[] = +{ // category name actor (function) okSafeMode + // --------------------- ------------------------ ----------------------- ---------- + { "spork", "spork", &spork, false}, + /** Not shown in help menu */ + { "hidden", "createsporkkeys", &createsporkkeys, false}, +}; +void RegisterSporkRPCCommands(CRPCTable &tableRPC) +{ + for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) + tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]); +} diff --git a/src/rpc/zeronode-budget.cpp b/src/rpc/zeronode-budget.cpp new file mode 100644 index 00000000000..9af5a570abf --- /dev/null +++ b/src/rpc/zeronode-budget.cpp @@ -0,0 +1,1066 @@ +// Copyright (c) 2014-2015 The Dash Developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zeronode/activezeronode.h" +#include "db.h" +#include "init.h" +#include "main.h" +#include "zeronode/budget.h" +#include "zeronode/payments.h" +#include "zeronode/zeronodeconfig.h" +#include "zeronode/zeronodeman.h" +#include "rpc/server.h" +#include "utilmoneystr.h" + +#include + +using namespace std; + +void budgetToJSON(CBudgetProposal* pbudgetProposal, UniValue& bObj) +{ + CTxDestination address1; + ExtractDestination(pbudgetProposal->GetPayee(), address1); + + bObj.push_back(Pair("Name", pbudgetProposal->GetName())); + bObj.push_back(Pair("URL", pbudgetProposal->GetURL())); + bObj.push_back(Pair("Hash", pbudgetProposal->GetHash().ToString())); + bObj.push_back(Pair("FeeHash", pbudgetProposal->nFeeTXHash.ToString())); + bObj.push_back(Pair("BlockStart", (int64_t)pbudgetProposal->GetBlockStart())); + bObj.push_back(Pair("BlockEnd", (int64_t)pbudgetProposal->GetBlockEnd())); + bObj.push_back(Pair("TotalPaymentCount", (int64_t)pbudgetProposal->GetTotalPaymentCount())); + bObj.push_back(Pair("RemainingPaymentCount", (int64_t)pbudgetProposal->GetRemainingPaymentCount())); + bObj.push_back(Pair("PaymentAddress", EncodeDestination(address1))); + bObj.push_back(Pair("Ratio", pbudgetProposal->GetRatio())); + bObj.push_back(Pair("Yeas", (int64_t)pbudgetProposal->GetYeas())); + bObj.push_back(Pair("Nays", (int64_t)pbudgetProposal->GetNays())); + bObj.push_back(Pair("Abstains", (int64_t)pbudgetProposal->GetAbstains())); + bObj.push_back(Pair("TotalPayment", ValueFromAmount(pbudgetProposal->GetAmount() * pbudgetProposal->GetTotalPaymentCount()))); + bObj.push_back(Pair("MonthlyPayment", ValueFromAmount(pbudgetProposal->GetAmount()))); + bObj.push_back(Pair("IsEstablished", pbudgetProposal->IsEstablished())); + + std::string strError = ""; + bObj.push_back(Pair("IsValid", pbudgetProposal->IsValid(strError))); + bObj.push_back(Pair("IsValidReason", strError.c_str())); + bObj.push_back(Pair("fValid", pbudgetProposal->fValid)); +} + + + +UniValue preparebudget(const UniValue& params, bool fHelp) +{ + int nBlockMin = 0; + CBlockIndex* pindexPrev = chainActive.Tip(); + + if (fHelp || params.size() != 6) + throw runtime_error( + "preparebudget \"proposal-name\" \"url\" payment-count block-start \"zero-address\" monthy-payment\n" + "\nPrepare proposal for network by signing and creating tx\n" + + "\nArguments:\n" + "1. \"proposal-name\": (string, required) Desired proposal name (20 character limit)\n" + "2. \"url\": (string, required) URL of proposal details (64 character limit)\n" + "3. payment-count: (numeric, required) Total number of monthly payments\n" + "4. block-start: (numeric, required) Starting super block height\n" + "5. \"zero-address\": (string, required) Zero address to send payments to\n" + "6. monthly-payment: (numeric, required) Monthly payment amount\n" + + "\nResult:\n" + "\"xxxx\" (string) proposal fee hash (if successful) or error message (if failed)\n" + "\nExamples:\n" + + HelpExampleCli("preparebudget", "\"test-proposal\" \"https://forum.zero.org/t/test-proposal\" 2 820800 \"D9oc6C3dttUbv8zd7zGNq1qKBGf4ZQ1XEE\" 500") + + HelpExampleRpc("preparebudget", "\"test-proposal\" \"https://forum.zero.org/t/test-proposal\" 2 820800 \"D9oc6C3dttUbv8zd7zGNq1qKBGf4ZQ1XEE\" 500")); + + if (pwalletMain->IsLocked()) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); + + std::string strProposalName = SanitizeString(params[0].get_str()); + if (strProposalName.size() > 20) + throw runtime_error("Invalid proposal name, limit of 20 characters."); + + std::string strURL = SanitizeString(params[1].get_str()); + if (strURL.size() > 64) + throw runtime_error("Invalid url, limit of 64 characters."); + + int nPaymentCount = params[2].get_int(); + if (nPaymentCount < 1) + throw runtime_error("Invalid payment count, must be more than zero."); + + // Start must be in the next budget cycle + if (pindexPrev != NULL) nBlockMin = pindexPrev->nHeight - pindexPrev->nHeight % GetBudgetPaymentCycleBlocks() + GetBudgetPaymentCycleBlocks(); + + int nBlockStart = params[3].get_int(); + if (nBlockStart % GetBudgetPaymentCycleBlocks() != 0) { + int nNext = pindexPrev->nHeight - pindexPrev->nHeight % GetBudgetPaymentCycleBlocks() + GetBudgetPaymentCycleBlocks(); + throw runtime_error(strprintf("Invalid block start - must be a budget cycle block. Next valid block: %d", nNext)); + } + + int nBlockEnd = nBlockStart + GetBudgetPaymentCycleBlocks() * nPaymentCount; // End must be AFTER current cycle + + if (nBlockStart < nBlockMin) + throw runtime_error("Invalid block start, must be more than current height."); + + if (nBlockEnd < pindexPrev->nHeight) + throw runtime_error("Invalid ending block, starting block + (payment_cycle*payments) must be more than current height."); + + CTxDestination address = DecodeDestination(params[4].get_str()); + if (!IsValidDestination(address)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zero address"); + + // Parse Zero address + CScript scriptPubKey = GetScriptForDestination(address); + CAmount nAmount = AmountFromValue(params[5]); + + //************************************************************************* + + // create transaction 15 minutes into the future, to allow for confirmation time + CBudgetProposalBroadcast budgetProposalBroadcast(strProposalName, strURL, nPaymentCount, scriptPubKey, nAmount, nBlockStart, uint256()); + + std::string strError = ""; + if (!budgetProposalBroadcast.IsValid(strError, false)) + throw runtime_error("Proposal is not valid - " + budgetProposalBroadcast.GetHash().ToString() + " - " + strError); + + bool useIX = false; //true; + // if (params.size() > 7) { + // if(params[7].get_str() != "false" && params[7].get_str() != "true") + // return "Invalid use_ix, must be true or false"; + // useIX = params[7].get_str() == "true" ? true : false; + // } + + CWalletTx wtx; + if (!pwalletMain->GetBudgetSystemCollateralTX(wtx, budgetProposalBroadcast.GetHash(), useIX)) { + throw runtime_error("Error making collateral transaction for proposal. Please check your wallet balance."); + } + + // make our change address + CReserveKey reservekey(pwalletMain); + //send the tx to the network + pwalletMain->CommitTransaction(wtx, reservekey, useIX ? "ix" : "tx"); + + return wtx.GetHash().ToString(); +} + +UniValue submitbudget(const UniValue& params, bool fHelp) +{ + int nBlockMin = 0; + CBlockIndex* pindexPrev = chainActive.Tip(); + + if (fHelp || params.size() != 7) + throw runtime_error( + "submitbudget \"proposal-name\" \"url\" payment-count block-start \"zero-address\" monthy-payment \"fee-tx\"\n" + "\nSubmit proposal to the network\n" + + "\nArguments:\n" + "1. \"proposal-name\": (string, required) Desired proposal name (20 character limit)\n" + "2. \"url\": (string, required) URL of proposal details (64 character limit)\n" + "3. payment-count: (numeric, required) Total number of monthly payments\n" + "4. block-start: (numeric, required) Starting super block height\n" + "5. \"zero-address\": (string, required) Zero address to send payments to\n" + "6. monthly-payment: (numeric, required) Monthly payment amount\n" + "7. \"fee-tx\": (string, required) Transaction hash from preparebudget command\n" + + "\nResult:\n" + "\"xxxx\" (string) proposal hash (if successful) or error message (if failed)\n" + "\nExamples:\n" + + HelpExampleCli("submitbudget", "\"test-proposal\" \"https://forum.zero.org/t/test-proposal\" 2 820800 \"D9oc6C3dttUbv8zd7zGNq1qKBGf4ZQ1XEE\" 500") + + HelpExampleRpc("submitbudget", "\"test-proposal\" \"https://forum.zero.org/t/test-proposal\" 2 820800 \"D9oc6C3dttUbv8zd7zGNq1qKBGf4ZQ1XEE\" 500")); + + // Check these inputs the same way we check the vote commands: + // ********************************************************** + + std::string strProposalName = SanitizeString(params[0].get_str()); + if (strProposalName.size() > 20) + throw runtime_error("Invalid proposal name, limit of 20 characters."); + + std::string strURL = SanitizeString(params[1].get_str()); + if (strURL.size() > 64) + throw runtime_error("Invalid url, limit of 64 characters."); + + int nPaymentCount = params[2].get_int(); + if (nPaymentCount < 1) + throw runtime_error("Invalid payment count, must be more than zero."); + + // Start must be in the next budget cycle + if (pindexPrev != NULL) nBlockMin = pindexPrev->nHeight - pindexPrev->nHeight % GetBudgetPaymentCycleBlocks() + GetBudgetPaymentCycleBlocks(); + + int nBlockStart = params[3].get_int(); + if (nBlockStart % GetBudgetPaymentCycleBlocks() != 0) { + int nNext = pindexPrev->nHeight - pindexPrev->nHeight % GetBudgetPaymentCycleBlocks() + GetBudgetPaymentCycleBlocks(); + throw runtime_error(strprintf("Invalid block start - must be a budget cycle block. Next valid block: %d", nNext)); + } + + int nBlockEnd = nBlockStart + (GetBudgetPaymentCycleBlocks() * nPaymentCount); // End must be AFTER current cycle + + if (nBlockStart < nBlockMin) + throw runtime_error("Invalid block start, must be more than current height."); + + if (nBlockEnd < pindexPrev->nHeight) + throw runtime_error("Invalid ending block, starting block + (payment_cycle*payments) must be more than current height."); + + CTxDestination address = DecodeDestination(params[4].get_str()); + if (!IsValidDestination(address)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zero address"); + + // Parse Zero address + CScript scriptPubKey = GetScriptForDestination(address); + CAmount nAmount = AmountFromValue(params[5]); + uint256 hash = ParseHashV(params[6], "parameter 1"); + + //create the proposal incase we're the first to make it + CBudgetProposalBroadcast budgetProposalBroadcast(strProposalName, strURL, nPaymentCount, scriptPubKey, nAmount, nBlockStart, hash); + + std::string strError = ""; + int nConf = 0; + if (!IsBudgetCollateralValid(hash, budgetProposalBroadcast.GetHash(), strError, budgetProposalBroadcast.nTime, nConf)) { + throw runtime_error("Proposal FeeTX is not valid - " + hash.ToString() + " - " + strError); + } + + if (!zeronodeSync.IsBlockchainSynced()) { + throw runtime_error("Must wait for client to sync with zeronode network. Try again in a minute or so."); + } + + // if(!budgetProposalBroadcast.IsValid(strError)){ + // return "Proposal is not valid - " + budgetProposalBroadcast.GetHash().ToString() + " - " + strError; + // } + + budget.mapSeenZeronodeBudgetProposals.insert(make_pair(budgetProposalBroadcast.GetHash(), budgetProposalBroadcast)); + budgetProposalBroadcast.Relay(); + if(budget.AddProposal(budgetProposalBroadcast)) { + return budgetProposalBroadcast.GetHash().ToString(); + } + throw runtime_error("Invalid proposal, see debug.log for details."); +} + +UniValue znbudgetvote(const UniValue& params, bool fHelp) +{ + std::string strCommand; + if (params.size() >= 1) { + strCommand = params[0].get_str(); + + // Backwards compatibility with legacy `znbudget` command + if (strCommand == "vote") strCommand = "local"; + if (strCommand == "vote-many") strCommand = "many"; + if (strCommand == "vote-alias") strCommand = "alias"; + } + + if (fHelp || (params.size() == 3 && (strCommand != "local" && strCommand != "many")) || (params.size() == 4 && strCommand != "alias") || + params.size() > 4 || params.size() < 3) + throw runtime_error( + "znbudgetvote \"local|many|alias\" \"votehash\" \"yes|no\" ( \"alias\" )\n" + "\nVote on a budget proposal\n" + + "\nArguments:\n" + "1. \"mode\" (string, required) The voting mode. 'local' for voting directly from a zeronode, 'many' for voting with a MN controller and casting the same vote for each MN, 'alias' for voting with a MN controller and casting a vote for a single MN\n" + "2. \"votehash\" (string, required) The vote hash for the proposal\n" + "3. \"votecast\" (string, required) Your vote. 'yes' to vote for the proposal, 'no' to vote against\n" + "4. \"alias\" (string, required for 'alias' mode) The MN alias to cast a vote for.\n" + + "\nResult:\n" + "{\n" + " \"overall\": \"xxxx\", (string) The overall status message for the vote cast\n" + " \"detail\": [\n" + " {\n" + " \"node\": \"xxxx\", (string) 'local' or the MN alias\n" + " \"result\": \"xxxx\", (string) Either 'Success' or 'Failed'\n" + " \"error\": \"xxxx\", (string) Error message, if vote failed\n" + " }\n" + " ,...\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("znbudgetvote", "\"local\" \"ed2f83cedee59a91406f5f47ec4d60bf5a7f9ee6293913c82976bd2d3a658041\" \"yes\"") + + HelpExampleRpc("znbudgetvote", "\"local\" \"ed2f83cedee59a91406f5f47ec4d60bf5a7f9ee6293913c82976bd2d3a658041\" \"yes\"")); + + uint256 hash = ParseHashV(params[1], "parameter 1"); + std::string strVote = params[2].get_str(); + + if (strVote != "yes" && strVote != "no") return "You can only vote 'yes' or 'no'"; + int nVote = VOTE_ABSTAIN; + if (strVote == "yes") nVote = VOTE_YES; + if (strVote == "no") nVote = VOTE_NO; + + int success = 0; + int failed = 0; + + UniValue resultsObj(UniValue::VARR); + + if (strCommand == "local") { + CPubKey pubKeyZeronode; + CKey keyZeronode; + std::string errorMessage; + + UniValue statusObj(UniValue::VOBJ); + + while (true) { + if (!obfuScationSigner.SetKey(strZeroNodePrivKey, errorMessage, keyZeronode, pubKeyZeronode)) { + failed++; + statusObj.push_back(Pair("node", "local")); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "Zeronode signing error, could not set key correctly: " + errorMessage)); + resultsObj.push_back(statusObj); + break; + } + + CZeronode* pzn = znodeman.Find(activeZeronode.vin); + if (pzn == NULL) { + failed++; + statusObj.push_back(Pair("node", "local")); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "Failure to find zeronode in list : " + activeZeronode.vin.ToString())); + resultsObj.push_back(statusObj); + break; + } + + CBudgetVote vote(activeZeronode.vin, hash, nVote); + if (!vote.Sign(keyZeronode, pubKeyZeronode)) { + failed++; + statusObj.push_back(Pair("node", "local")); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "Failure to sign.")); + resultsObj.push_back(statusObj); + break; + } + + std::string strError = ""; + if (budget.UpdateProposal(vote, NULL, strError)) { + success++; + budget.mapSeenZeronodeBudgetVotes.insert(make_pair(vote.GetHash(), vote)); + vote.Relay(); + statusObj.push_back(Pair("node", "local")); + statusObj.push_back(Pair("result", "success")); + statusObj.push_back(Pair("error", "")); + } else { + failed++; + statusObj.push_back(Pair("node", "local")); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "Error voting : " + strError)); + } + resultsObj.push_back(statusObj); + break; + } + + UniValue returnObj(UniValue::VOBJ); + returnObj.push_back(Pair("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", success, failed))); + returnObj.push_back(Pair("detail", resultsObj)); + + return returnObj; + } + + if (strCommand == "many") { + BOOST_FOREACH (CZeronodeConfig::CZeronodeEntry zne, zeronodeConfig.getEntries()) { + std::string errorMessage; + std::vector vchZeroNodeSignature; + std::string strZeroNodeSignMessage; + + CPubKey pubKeyCollateralAddress; + CKey keyCollateralAddress; + CPubKey pubKeyZeronode; + CKey keyZeronode; + + UniValue statusObj(UniValue::VOBJ); + + if (!obfuScationSigner.SetKey(zne.getPrivKey(), errorMessage, keyZeronode, pubKeyZeronode)) { + failed++; + statusObj.push_back(Pair("node", zne.getAlias())); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "Zeronode signing error, could not set key correctly: " + errorMessage)); + resultsObj.push_back(statusObj); + continue; + } + + CZeronode* pzn = znodeman.Find(pubKeyZeronode); + if (pzn == NULL) { + failed++; + statusObj.push_back(Pair("node", zne.getAlias())); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "Can't find zeronode by pubkey")); + resultsObj.push_back(statusObj); + continue; + } + + CBudgetVote vote(pzn->vin, hash, nVote); + if (!vote.Sign(keyZeronode, pubKeyZeronode)) { + failed++; + statusObj.push_back(Pair("node", zne.getAlias())); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "Failure to sign.")); + resultsObj.push_back(statusObj); + continue; + } + + std::string strError = ""; + if (budget.UpdateProposal(vote, NULL, strError)) { + budget.mapSeenZeronodeBudgetVotes.insert(make_pair(vote.GetHash(), vote)); + vote.Relay(); + success++; + statusObj.push_back(Pair("node", zne.getAlias())); + statusObj.push_back(Pair("result", "success")); + statusObj.push_back(Pair("error", "")); + } else { + failed++; + statusObj.push_back(Pair("node", zne.getAlias())); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", strError.c_str())); + } + + resultsObj.push_back(statusObj); + } + + UniValue returnObj(UniValue::VOBJ); + returnObj.push_back(Pair("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", success, failed))); + returnObj.push_back(Pair("detail", resultsObj)); + + return returnObj; + } + + if (strCommand == "alias") { + std::string strAlias = params[3].get_str(); + std::vector znEntries; + znEntries = zeronodeConfig.getEntries(); + + BOOST_FOREACH(CZeronodeConfig::CZeronodeEntry zne, zeronodeConfig.getEntries()) { + + if( strAlias != zne.getAlias()) continue; + + std::string errorMessage; + std::vector vchZeroNodeSignature; + std::string strZeroNodeSignMessage; + + CPubKey pubKeyCollateralAddress; + CKey keyCollateralAddress; + CPubKey pubKeyZeronode; + CKey keyZeronode; + + UniValue statusObj(UniValue::VOBJ); + + if(!obfuScationSigner.SetKey(zne.getPrivKey(), errorMessage, keyZeronode, pubKeyZeronode)){ + failed++; + statusObj.push_back(Pair("node", zne.getAlias())); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "Zeronode signing error, could not set key correctly: " + errorMessage)); + resultsObj.push_back(statusObj); + continue; + } + + CZeronode* pzn = znodeman.Find(pubKeyZeronode); + if(pzn == NULL) + { + failed++; + statusObj.push_back(Pair("node", zne.getAlias())); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "Can't find zeronode by pubkey")); + resultsObj.push_back(statusObj); + continue; + } + + CBudgetVote vote(pzn->vin, hash, nVote); + if(!vote.Sign(keyZeronode, pubKeyZeronode)){ + failed++; + statusObj.push_back(Pair("node", zne.getAlias())); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "Failure to sign.")); + resultsObj.push_back(statusObj); + continue; + } + + std::string strError = ""; + if(budget.UpdateProposal(vote, NULL, strError)) { + budget.mapSeenZeronodeBudgetVotes.insert(make_pair(vote.GetHash(), vote)); + vote.Relay(); + success++; + statusObj.push_back(Pair("node", zne.getAlias())); + statusObj.push_back(Pair("result", "success")); + statusObj.push_back(Pair("error", "")); + } else { + failed++; + statusObj.push_back(Pair("node", zne.getAlias())); + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", strError.c_str())); + } + + resultsObj.push_back(statusObj); + } + + UniValue returnObj(UniValue::VOBJ); + returnObj.push_back(Pair("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", success, failed))); + returnObj.push_back(Pair("detail", resultsObj)); + + return returnObj; + } + + return NullUniValue; +} + +UniValue getbudgetvotes(const UniValue& params, bool fHelp) +{ + if (params.size() != 1) + throw runtime_error( + "getbudgetvotes \"proposal-name\"\n" + "\nPrint vote information for a budget proposal\n" + + "\nArguments:\n" + "1. \"proposal-name\": (string, required) Name of the proposal\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"znId\": \"xxxx\", (string) Hash of the zeronode's collateral transaction\n" + " \"nHash\": \"xxxx\", (string) Hash of the vote\n" + " \"Vote\": \"YES|NO\", (string) Vote cast ('YES' or 'NO')\n" + " \"nTime\": xxxx, (numeric) Time in seconds since epoch the vote was cast\n" + " \"fValid\": true|false, (boolean) 'true' if the vote is valid, 'false' otherwise\n" + " }\n" + " ,...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getbudgetvotes", "\"test-proposal\"") + HelpExampleRpc("getbudgetvotes", "\"test-proposal\"")); + + std::string strProposalName = SanitizeString(params[0].get_str()); + + UniValue ret(UniValue::VARR); + + CBudgetProposal* pbudgetProposal = budget.FindProposal(strProposalName); + + if (pbudgetProposal == NULL) throw runtime_error("Unknown proposal name"); + + std::map::iterator it = pbudgetProposal->mapVotes.begin(); + while (it != pbudgetProposal->mapVotes.end()) { + UniValue bObj(UniValue::VOBJ); + bObj.push_back(Pair("znId", (*it).second.vin.prevout.hash.ToString())); + bObj.push_back(Pair("nHash", (*it).first.ToString().c_str())); + bObj.push_back(Pair("Vote", (*it).second.GetVoteString())); + bObj.push_back(Pair("nTime", (int64_t)(*it).second.nTime)); + bObj.push_back(Pair("fValid", (*it).second.fValid)); + + ret.push_back(bObj); + + it++; + } + + return ret; +} + +UniValue getnextsuperblock(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getnextsuperblock\n" + "\nPrint the next super block height\n" + + "\nResult:\n" + "n (numeric) Block height of the next super block\n" + "\nExamples:\n" + + HelpExampleCli("getnextsuperblock", "") + HelpExampleRpc("getnextsuperblock", "")); + + CBlockIndex* pindexPrev = chainActive.Tip(); + if (!pindexPrev) return "unknown"; + + int nNext = pindexPrev->nHeight - pindexPrev->nHeight % GetBudgetPaymentCycleBlocks() + GetBudgetPaymentCycleBlocks(); + return nNext; +} + +UniValue getbudgetprojection(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "getbudgetprojection\n" + "\nShow the projection of which proposals will be paid the next cycle\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"Name\": \"xxxx\", (string) Proposal Name\n" + " \"URL\": \"xxxx\", (string) Proposal URL\n" + " \"Hash\": \"xxxx\", (string) Proposal vote hash\n" + " \"FeeHash\": \"xxxx\", (string) Proposal fee hash\n" + " \"BlockStart\": n, (numeric) Proposal starting block\n" + " \"BlockEnd\": n, (numeric) Proposal ending block\n" + " \"TotalPaymentCount\": n, (numeric) Number of payments\n" + " \"RemainingPaymentCount\": n, (numeric) Number of remaining payments\n" + " \"PaymentAddress\": \"xxxx\", (string) Zero address of payment\n" + " \"Ratio\": x.xxx, (numeric) Ratio of yeas vs nays\n" + " \"Yeas\": n, (numeric) Number of yea votes\n" + " \"Nays\": n, (numeric) Number of nay votes\n" + " \"Abstains\": n, (numeric) Number of abstains\n" + " \"TotalPayment\": xxx.xxx, (numeric) Total payment amount\n" + " \"MonthlyPayment\": xxx.xxx, (numeric) Monthly payment amount\n" + " \"IsEstablished\": true|false, (boolean) Established (true) or (false)\n" + " \"IsValid\": true|false, (boolean) Valid (true) or Invalid (false)\n" + " \"IsValidReason\": \"xxxx\", (string) Error message, if any\n" + " \"fValid\": true|false, (boolean) Valid (true) or Invalid (false)\n" + " \"Alloted\": xxx.xxx, (numeric) Amount alloted in current period\n" + " \"TotalBudgetAlloted\": xxx.xxx (numeric) Total alloted\n" + " }\n" + " ,...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getbudgetprojection", "") + HelpExampleRpc("getbudgetprojection", "")); + + UniValue ret(UniValue::VARR); + UniValue resultObj(UniValue::VOBJ); + CAmount nTotalAllotted = 0; + + std::vector winningProps = budget.GetBudget(); + BOOST_FOREACH (CBudgetProposal* pbudgetProposal, winningProps) { + nTotalAllotted += pbudgetProposal->GetAllotted(); + + //CTxDestination address1; + //ExtractDestination(pbudgetProposal->GetPayee(), address1); + //CTxDestination address2 = DecodeDestination(address1); + + UniValue bObj(UniValue::VOBJ); + budgetToJSON(pbudgetProposal, bObj); + bObj.push_back(Pair("Alloted", ValueFromAmount(pbudgetProposal->GetAllotted()))); + bObj.push_back(Pair("TotalBudgetAlloted", ValueFromAmount(nTotalAllotted))); + + ret.push_back(bObj); + } + + return ret; +} + +UniValue getbudgetinfo(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "getbudgetinfo ( \"proposal\" )\n" + "\nShow current zeronode budgets\n" + + "\nArguments:\n" + "1. \"proposal\" (string, optional) Proposal name\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"Name\": \"xxxx\", (string) Proposal Name\n" + " \"URL\": \"xxxx\", (string) Proposal URL\n" + " \"Hash\": \"xxxx\", (string) Proposal vote hash\n" + " \"FeeHash\": \"xxxx\", (string) Proposal fee hash\n" + " \"BlockStart\": n, (numeric) Proposal starting block\n" + " \"BlockEnd\": n, (numeric) Proposal ending block\n" + " \"TotalPaymentCount\": n, (numeric) Number of payments\n" + " \"RemainingPaymentCount\": n, (numeric) Number of remaining payments\n" + " \"PaymentAddress\": \"xxxx\", (string) Zero address of payment\n" + " \"Ratio\": x.xxx, (numeric) Ratio of yeas vs nays\n" + " \"Yeas\": n, (numeric) Number of yea votes\n" + " \"Nays\": n, (numeric) Number of nay votes\n" + " \"Abstains\": n, (numeric) Number of abstains\n" + " \"TotalPayment\": xxx.xxx, (numeric) Total payment amount\n" + " \"MonthlyPayment\": xxx.xxx, (numeric) Monthly payment amount\n" + " \"IsEstablished\": true|false, (boolean) Established (true) or (false)\n" + " \"IsValid\": true|false, (boolean) Valid (true) or Invalid (false)\n" + " \"IsValidReason\": \"xxxx\", (string) Error message, if any\n" + " \"fValid\": true|false, (boolean) Valid (true) or Invalid (false)\n" + " }\n" + " ,...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getbudgetprojection", "") + HelpExampleRpc("getbudgetprojection", "")); + + UniValue ret(UniValue::VARR); + + std::string strShow = "valid"; + if (params.size() == 1) { + std::string strProposalName = SanitizeString(params[0].get_str()); + CBudgetProposal* pbudgetProposal = budget.FindProposal(strProposalName); + if (pbudgetProposal == NULL) throw runtime_error("Unknown proposal name"); + UniValue bObj(UniValue::VOBJ); + budgetToJSON(pbudgetProposal, bObj); + ret.push_back(bObj); + return ret; + } + + std::vector winningProps = budget.GetAllProposals(); + BOOST_FOREACH (CBudgetProposal* pbudgetProposal, winningProps) { + if (strShow == "valid" && !pbudgetProposal->fValid) continue; + + UniValue bObj(UniValue::VOBJ); + budgetToJSON(pbudgetProposal, bObj); + + ret.push_back(bObj); + } + + return ret; +} + +UniValue znbudgetrawvote(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 6) + throw runtime_error( + "znbudgetrawvote \"zeronode-tx-hash\" zeronode-tx-index \"proposal-hash\" yes|no time \"vote-sig\"\n" + "\nCompile and relay a proposal vote with provided external signature instead of signing vote internally\n" + + "\nArguments:\n" + "1. \"zeronode-tx-hash\" (string, required) Transaction hash for the zeronode\n" + "2. zeronode-tx-index (numeric, required) Output index for the zeronode\n" + "3. \"proposal-hash\" (string, required) Proposal vote hash\n" + "4. yes|no (boolean, required) Vote to cast\n" + "5. time (numeric, required) Time since epoch in seconds\n" + "6. \"vote-sig\" (string, required) External signature\n" + + "\nResult:\n" + "\"status\" (string) Vote status or error message\n" + "\nExamples:\n" + + HelpExampleCli("znbudgetrawvote", "") + HelpExampleRpc("znbudgetrawvote", "")); + + uint256 hashMnTx = ParseHashV(params[0], "zn tx hash"); + int nMnTxIndex = params[1].get_int(); + CTxIn vin = CTxIn(hashMnTx, nMnTxIndex); + + uint256 hashProposal = ParseHashV(params[2], "Proposal hash"); + std::string strVote = params[3].get_str(); + + if (strVote != "yes" && strVote != "no") return "You can only vote 'yes' or 'no'"; + int nVote = VOTE_ABSTAIN; + if (strVote == "yes") nVote = VOTE_YES; + if (strVote == "no") nVote = VOTE_NO; + + int64_t nTime = params[4].get_int64(); + std::string strSig = params[5].get_str(); + bool fInvalid = false; + vector vchSig = DecodeBase64(strSig.c_str(), &fInvalid); + + if (fInvalid) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding"); + + CZeronode* pzn = znodeman.Find(vin); + if (pzn == NULL) { + return "Failure to find zeronode in list : " + vin.ToString(); + } + + CBudgetVote vote(vin, hashProposal, nVote); + vote.nTime = nTime; + vote.vchSig = vchSig; + + if (!vote.SignatureValid(true)) { + return "Failure to verify signature."; + } + + std::string strError = ""; + if (budget.UpdateProposal(vote, NULL, strError)) { + budget.mapSeenZeronodeBudgetVotes.insert(make_pair(vote.GetHash(), vote)); + vote.Relay(); + return "Voted successfully"; + } else { + return "Error voting : " + strError; + } +} + +UniValue znfinalbudget(const UniValue& params, bool fHelp) +{ + string strCommand; + if (params.size() >= 1) + strCommand = params[0].get_str(); + + if (fHelp || + (strCommand != "suggest" && strCommand != "vote-many" && strCommand != "vote" && strCommand != "show" && strCommand != "getvotes")) + throw runtime_error( + "znfinalbudget \"command\"... ( \"passphrase\" )\n" + "Vote or show current budgets\n" + "\nAvailable commands:\n" + " vote-many - Vote on a finalized budget\n" + " vote - Vote on a finalized budget\n" + " show - Show existing finalized budgets\n" + " getvotes - Get vote information for each finalized budget\n"); + + if (strCommand == "vote-many") { + if (params.size() != 2) + throw runtime_error("Correct usage is 'znfinalbudget vote-many BUDGET_HASH'"); + + std::string strHash = params[1].get_str(); + uint256 hash(uint256S(strHash)); + + int success = 0; + int failed = 0; + + UniValue resultsObj(UniValue::VOBJ); + + BOOST_FOREACH (CZeronodeConfig::CZeronodeEntry zne, zeronodeConfig.getEntries()) { + std::string errorMessage; + std::vector vchZeroNodeSignature; + std::string strZeroNodeSignMessage; + + CPubKey pubKeyCollateralAddress; + CKey keyCollateralAddress; + CPubKey pubKeyZeronode; + CKey keyZeronode; + + UniValue statusObj(UniValue::VOBJ); + + if (!obfuScationSigner.SetKey(zne.getPrivKey(), errorMessage, keyZeronode, pubKeyZeronode)) { + failed++; + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("errorMessage", "Zeronode signing error, could not set key correctly: " + errorMessage)); + resultsObj.push_back(Pair(zne.getAlias(), statusObj)); + continue; + } + + CZeronode* pzn = znodeman.Find(pubKeyZeronode); + if (pzn == NULL) { + failed++; + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("errorMessage", "Can't find zeronode by pubkey")); + resultsObj.push_back(Pair(zne.getAlias(), statusObj)); + continue; + } + + + CFinalizedBudgetVote vote(pzn->vin, hash); + if (!vote.Sign(keyZeronode, pubKeyZeronode)) { + failed++; + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("errorMessage", "Failure to sign.")); + resultsObj.push_back(Pair(zne.getAlias(), statusObj)); + continue; + } + + std::string strError = ""; + if (budget.UpdateFinalizedBudget(vote, NULL, strError)) { + budget.mapSeenFinalizedBudgetVotes.insert(make_pair(vote.GetHash(), vote)); + vote.Relay(); + success++; + statusObj.push_back(Pair("result", "success")); + } else { + failed++; + statusObj.push_back(Pair("result", strError.c_str())); + } + + resultsObj.push_back(Pair(zne.getAlias(), statusObj)); + } + + UniValue returnObj(UniValue::VOBJ); + returnObj.push_back(Pair("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", success, failed))); + returnObj.push_back(Pair("detail", resultsObj)); + + return returnObj; + } + + if (strCommand == "vote") { + if (params.size() != 2) + throw runtime_error("Correct usage is 'znfinalbudget vote BUDGET_HASH'"); + + std::string strHash = params[1].get_str(); + uint256 hash; + hash = ArithToUint256(arith_uint256(strHash)); + + CPubKey pubKeyZeronode; + CKey keyZeronode; + std::string errorMessage; + + if (!obfuScationSigner.SetKey(strZeroNodePrivKey, errorMessage, keyZeronode, pubKeyZeronode)) + return "Error upon calling SetKey"; + + CZeronode* pzn = znodeman.Find(activeZeronode.vin); + if (pzn == NULL) { + return "Failure to find zeronode in list : " + activeZeronode.vin.ToString(); + } + + CFinalizedBudgetVote vote(activeZeronode.vin, hash); + if (!vote.Sign(keyZeronode, pubKeyZeronode)) { + return "Failure to sign."; + } + + std::string strError = ""; + if (budget.UpdateFinalizedBudget(vote, NULL, strError)) { + budget.mapSeenFinalizedBudgetVotes.insert(make_pair(vote.GetHash(), vote)); + vote.Relay(); + return "success"; + } else { + return "Error voting : " + strError; + } + } + + if (strCommand == "show") { + UniValue resultObj(UniValue::VOBJ); + + std::vector winningFbs = budget.GetFinalizedBudgets(); + BOOST_FOREACH (CFinalizedBudget* finalizedBudget, winningFbs) { + UniValue bObj(UniValue::VOBJ); + bObj.push_back(Pair("FeeTX", finalizedBudget->nFeeTXHash.ToString())); + bObj.push_back(Pair("Hash", finalizedBudget->GetHash().ToString())); + bObj.push_back(Pair("BlockStart", (int64_t)finalizedBudget->GetBlockStart())); + bObj.push_back(Pair("BlockEnd", (int64_t)finalizedBudget->GetBlockEnd())); + bObj.push_back(Pair("Proposals", finalizedBudget->GetProposals())); + bObj.push_back(Pair("VoteCount", (int64_t)finalizedBudget->GetVoteCount())); + bObj.push_back(Pair("Status", finalizedBudget->GetStatus())); + + std::string strError = ""; + bObj.push_back(Pair("IsValid", finalizedBudget->IsValid(strError))); + bObj.push_back(Pair("IsValidReason", strError.c_str())); + + resultObj.push_back(Pair(finalizedBudget->GetName(), bObj)); + } + + return resultObj; + } + + if (strCommand == "getvotes") { + if (params.size() != 2) + throw runtime_error("Correct usage is 'znbudget getvotes budget-hash'"); + + std::string strHash = params[1].get_str(); + uint256 hash; + hash = ArithToUint256(arith_uint256(strHash)); + + UniValue obj(UniValue::VOBJ); + + CFinalizedBudget* pfinalBudget = budget.FindFinalizedBudget(hash); + + if (pfinalBudget == NULL) return "Unknown budget hash"; + + std::map::iterator it = pfinalBudget->mapVotes.begin(); + while (it != pfinalBudget->mapVotes.end()) { + UniValue bObj(UniValue::VOBJ); + bObj.push_back(Pair("nHash", (*it).first.ToString().c_str())); + bObj.push_back(Pair("nTime", (int64_t)(*it).second.nTime)); + bObj.push_back(Pair("fValid", (*it).second.fValid)); + + obj.push_back(Pair((*it).second.vin.prevout.ToStringShort(), bObj)); + + it++; + } + + return obj; + } + + return NullUniValue; +} + +UniValue checkbudgets(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "checkbudgets\n" + "\nInitiates a buddget check cycle manually\n" + "\nExamples:\n" + + HelpExampleCli("checkbudgets", "") + HelpExampleRpc("checkbudgets", "")); + + budget.CheckAndRemove(); + + return NullUniValue; +} + + +// This command is retained for backwards compatibility, but is depreciated. +// Future removal of this command is planned to keep things clean. +UniValue znbudget(const UniValue& params, bool fHelp) +{ + string strCommand; + if (params.size() >= 1) + strCommand = params[0].get_str(); + + if (fHelp || + (strCommand != "vote-alias" && strCommand != "vote-many" && strCommand != "prepare" && strCommand != "submit" && strCommand != "vote" && strCommand != "getvotes" && strCommand != "getinfo" && strCommand != "show" && strCommand != "projection" && strCommand != "check" && strCommand != "nextblock")) + throw runtime_error( + "znbudget \"command\"... ( \"passphrase\" )\n" + "\nVote or show current budgets\n" + "This command is depreciated, please see individual command documentation for future reference\n\n" + + "\nAvailable commands:\n" + " prepare - Prepare proposal for network by signing and creating tx\n" + " submit - Submit proposal for network\n" + " vote-many - Vote on a Zero initiative\n" + " vote-alias - Vote on a Zero initiative\n" + " vote - Vote on a Zero initiative/budget\n" + " getvotes - Show current zeronode budgets\n" + " getinfo - Show current zeronode budgets\n" + " show - Show all budgets\n" + " projection - Show the projection of which proposals will be paid the next cycle\n" + " check - Scan proposals and remove invalid\n" + " nextblock - Get next superblock for budget system\n"); + + if (strCommand == "nextblock") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return getnextsuperblock(newParams, fHelp); + } + + if (strCommand == "prepare") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return preparebudget(newParams, fHelp); + } + + if (strCommand == "submit") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return submitbudget(newParams, fHelp); + } + + if (strCommand == "vote" || strCommand == "vote-many" || strCommand == "vote-alias") { + if (strCommand == "vote-alias") + throw runtime_error( + "vote-alias is not supported with this command\n" + "Please use znbudgetvote instead.\n" + ); + return znbudgetvote(params, fHelp); + } + + if (strCommand == "projection") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return getbudgetprojection(newParams, fHelp); + } + + if (strCommand == "show" || strCommand == "getinfo") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return getbudgetinfo(newParams, fHelp); + } + + if (strCommand == "getvotes") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return getbudgetvotes(newParams, fHelp); + } + + if (strCommand == "check") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return checkbudgets(newParams, fHelp); + } + + return NullUniValue; +} + + + +static const CRPCCommand commands[] = +{ // category name actor (function) okSafeMode + // --------------------- ------------------------ ----------------------- ---------- + { "zeronode-budget", "znbudget", &znbudget, true }, + { "zeronode-budget", "preparebudget", &preparebudget, true }, + { "zeronode-budget", "submitbudget", &submitbudget, true }, + { "zeronode-budget", "znbudgetvote", &znbudgetvote, true }, + { "zeronode-budget", "getbudgetvotes", &getbudgetvotes, true }, + { "zeronode-budget", "getnextsuperblock", &getnextsuperblock, true }, + { "zeronode-budget", "getbudgetprojection", &getbudgetprojection, true }, + { "zeronode-budget", "getbudgetinfo", &getbudgetinfo, true }, + { "zeronode-budget", "znbudgetrawvote", &znbudgetrawvote, true }, + { "zeronode-budget", "znfinalbudget", &znfinalbudget, true }, + { "zeronode-budget", "znbudgetvote", &znbudgetvote, true }, + { "zeronode-budget", "checkbudgets", &checkbudgets, true }, +}; + +void RegisterBudgetRPCCommands(CRPCTable &tableRPC) +{ + for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) + tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]); +} diff --git a/src/rpc/zeronode.cpp b/src/rpc/zeronode.cpp new file mode 100644 index 00000000000..98ffb7cf559 --- /dev/null +++ b/src/rpc/zeronode.cpp @@ -0,0 +1,1006 @@ +// Copyright (c) 2009-2012 The Bitcoin developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zeronode/activezeronode.h" +#include "db.h" +#include "init.h" +#include "main.h" +#include "zeronode/budget.h" +#include "zeronode/payments.h" +#include "zeronode/zeronodeconfig.h" +#include "zeronode/zeronodeman.h" +#include "zeronode/zeronode-sync.h" +#include "rpc/server.h" +#include "utilmoneystr.h" + +#include + +#include + +UniValue listzeronodes(const UniValue& params, bool fHelp) +{ + std::string strFilter = ""; + + if (params.size() == 1) strFilter = params[0].get_str(); + + if (fHelp || (params.size() > 1)) + throw runtime_error( + "listzeronodes ( \"filter\" )\n" + "\nGet a ranked list of zeronodes\n" + + "\nArguments:\n" + "1. \"filter\" (string, optional) Filter search text. Partial match by txhash, status, or addr.\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"rank\": n, (numeric) Zeronode Rank (or 0 if not enabled)\n" + " \"txhash\": \"hash\", (string) Collateral transaction hash\n" + " \"outidx\": n, (numeric) Collateral transaction output index\n" + " \"status\": s, (string) Status (ENABLED/EXPIRED/REMOVE/etc)\n" + " \"addr\": \"addr\", (string) Zeronode Zero address\n" + " \"version\": v, (numeric) Zeronode protocol version\n" + " \"lastseen\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last seen\n" + " \"activetime\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) zeronode has been active\n" + " \"lastpaid\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) zeronode was last paid\n" + " }\n" + " ,...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("zeronodelist", "") + HelpExampleRpc("zeronodelist", "")); + + UniValue ret(UniValue::VARR); + int nHeight; + { + LOCK(cs_main); + CBlockIndex* pindex = chainActive.Tip(); + if(!pindex) return 0; + nHeight = pindex->nHeight; + } + std::vector > vZeronodeRanks = znodeman.GetZeronodeRanks(nHeight); + BOOST_FOREACH (PAIRTYPE(int, CZeronode) & s, vZeronodeRanks) { + UniValue obj(UniValue::VOBJ); + std::string strVin = s.second.vin.prevout.ToStringShort(); + std::string strTxHash = s.second.vin.prevout.hash.ToString(); + uint32_t oIdx = s.second.vin.prevout.n; + + CZeronode* zn = znodeman.Find(s.second.vin); + + if (zn != NULL) { + if (strFilter != "" && strTxHash.find(strFilter) == string::npos && + zn->Status().find(strFilter) == string::npos && + EncodeDestination(zn->pubKeyCollateralAddress.GetID()).find(strFilter) == string::npos) continue; + + std::string strStatus = zn->Status(); + std::string strHost; + int port; + SplitHostPort(zn->addr.ToString(), port, strHost); + CNetAddr node = CNetAddr(strHost, false); + std::string strNetwork = GetNetworkName(node.GetNetwork()); + + obj.push_back(Pair("rank", (strStatus == "ENABLED" ? s.first : 0))); + obj.push_back(Pair("network", strNetwork)); + obj.push_back(Pair("ip", strHost)); + obj.push_back(Pair("txhash", strTxHash)); + obj.push_back(Pair("outidx", (uint64_t)oIdx)); + obj.push_back(Pair("status", strStatus)); + obj.push_back(Pair("addr", EncodeDestination(zn->pubKeyCollateralAddress.GetID()))); + obj.push_back(Pair("version", zn->protocolVersion)); + obj.push_back(Pair("lastseen", (int64_t)zn->lastPing.sigTime)); + obj.push_back(Pair("activetime", (int64_t)(zn->lastPing.sigTime - zn->sigTime))); + obj.push_back(Pair("lastpaid", (int64_t)zn->GetLastPaid())); + + ret.push_back(obj); + } + } + + return ret; +} + +UniValue startalias(const UniValue& params, bool fHelp) +{ + LogPrintf("params.size(): %d", params.size()); + if (fHelp || (params.size() != 1)) + throw runtime_error( + "startalias \"aliasname\"\n" + "\nAttempts to start an alias\n" + + "\nArguments:\n" + "1. \"aliasname\" (string, required) alias name\n" + + "\nExamples:\n" + + HelpExampleCli("startalias", "\"zn1\"") + HelpExampleRpc("startalias", "")); + if (!zeronodeSync.IsSynced()) + { + UniValue obj(UniValue::VOBJ); + std::string error = "Zeronode is not synced, please wait. Current status: " + zeronodeSync.GetSyncStatus(); + obj.push_back(Pair("result", error)); + return obj; + } + + std::string strAlias = params[0].get_str(); + bool fSuccess = false; + BOOST_FOREACH (CZeronodeConfig::CZeronodeEntry zne, zeronodeConfig.getEntries()) { + if (zne.getAlias() == strAlias) { + std::string strError; + CZeronodeBroadcast znb; + + fSuccess = CZeronodeBroadcast::Create(zne.getIp(), zne.getPrivKey(), zne.getTxHash(), zne.getOutputIndex(), strError, znb); + + if (fSuccess) { + znodeman.UpdateZeronodeList(znb); + znb.Relay(); + } + break; + } + } + if (fSuccess) { + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("result", "Successfully started alias")); + return obj; + } else { + throw runtime_error("Failed to start alias\n"); + } +} + +UniValue zeronodeconnect(const UniValue& params, bool fHelp) +{ + if (fHelp || (params.size() != 1)) + throw runtime_error( + "zeronodeconnect \"address\"\n" + "\nAttempts to connect to specified zeronode address\n" + + "\nArguments:\n" + "1. \"address\" (string, required) IP or net address to connect to\n" + + "\nExamples:\n" + + HelpExampleCli("zeronodeconnect", "\"192.168.0.6:23821\"") + HelpExampleRpc("zeronodeconnect", "\"192.168.0.6:23821\"")); + + std::string strAddress = params[0].get_str(); + + CService addr = CService(strAddress); + + CNode* pnode = ConnectNode((CAddress)addr, NULL, false); + if (pnode) { + pnode->Release(); + return NullUniValue; + } else { + throw runtime_error("error connecting\n"); + } +} + +UniValue getzeronodecount (const UniValue& params, bool fHelp) +{ + if (fHelp || (params.size() > 0)) + throw runtime_error( + "getzeronodecount\n" + "\nGet zeronode count values\n" + + "\nResult:\n" + "{\n" + " \"total\": n, (numeric) Total zeronodes\n" + " \"stable\": n, (numeric) Stable count\n" + " \"obfcompat\": n, (numeric) Obfuscation Compatible\n" + " \"enabled\": n, (numeric) Enabled zeronodes\n" + " \"inqueue\": n (numeric) Zeronodes in queue\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("getzeronodecount", "") + HelpExampleRpc("getzeronodecount", "")); + + UniValue obj(UniValue::VOBJ); + int nCount = 0; + int ipv4 = 0, ipv6 = 0, onion = 0; + + if (chainActive.Tip()) + znodeman.GetNextZeronodeInQueueForPayment(chainActive.Tip()->nHeight, true, nCount); + + znodeman.CountNetworks(ActiveProtocol(), ipv4, ipv6, onion); + + obj.push_back(Pair("total", znodeman.size())); + obj.push_back(Pair("stable", znodeman.stable_size())); + obj.push_back(Pair("obfcompat", znodeman.CountEnabled(ActiveProtocol()))); + obj.push_back(Pair("enabled", znodeman.CountEnabled())); + obj.push_back(Pair("inqueue", nCount)); + obj.push_back(Pair("ipv4", ipv4)); + obj.push_back(Pair("ipv6", ipv6)); + obj.push_back(Pair("onion", onion)); + + return obj; +} + +UniValue zeronodecurrent (const UniValue& params, bool fHelp) +{ + if (fHelp || (params.size() != 0)) + throw runtime_error( + "zeronodecurrent\n" + "\nGet current zeronode winner\n" + + "\nResult:\n" + "{\n" + " \"protocol\": xxxx, (numeric) Protocol version\n" + " \"txhash\": \"xxxx\", (string) Collateral transaction hash\n" + " \"pubkey\": \"xxxx\", (string) MN Public key\n" + " \"lastseen\": xxx, (numeric) Time since epoch of last seen\n" + " \"activeseconds\": xxx, (numeric) Seconds MN has been active\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("zeronodecurrent", "") + HelpExampleRpc("zeronodecurrent", "")); + + CZeronode* winner = znodeman.GetCurrentZeroNode(1); + if (winner) { + UniValue obj(UniValue::VOBJ); + + obj.push_back(Pair("protocol", (int64_t)winner->protocolVersion)); + obj.push_back(Pair("txhash", winner->vin.prevout.hash.ToString())); + obj.push_back(Pair("pubkey", EncodeDestination(winner->pubKeyCollateralAddress.GetID()))); + obj.push_back(Pair("lastseen", (winner->lastPing == CZeronodePing()) ? winner->sigTime : (int64_t)winner->lastPing.sigTime)); + obj.push_back(Pair("activeseconds", (winner->lastPing == CZeronodePing()) ? 0 : (int64_t)(winner->lastPing.sigTime - winner->sigTime))); + return obj; + } + + throw runtime_error("unknown"); +} + +UniValue zeronodedebug (const UniValue& params, bool fHelp) +{ + if (fHelp || (params.size() != 0)) + throw runtime_error( + "zeronodedebug\n" + "\nPrint zeronode status\n" + + "\nResult:\n" + "\"status\" (string) Zeronode status message\n" + "\nExamples:\n" + + HelpExampleCli("zeronodedebug", "") + HelpExampleRpc("zeronodedebug", "")); + + if (activeZeronode.status != ACTIVE_ZERONODE_INITIAL || !zeronodeSync.IsSynced()) + return activeZeronode.GetStatus(); + + CTxIn vin = CTxIn(); + CPubKey pubkey; + CKey key; + if (!activeZeronode.GetZeroNodeVin(vin, pubkey, key)) + throw runtime_error("Missing zeronode input, please look at the documentation for instructions on zeronode creation\n"); + else + return activeZeronode.GetStatus(); +} + +UniValue startzeronode (const UniValue& params, bool fHelp) +{ + std::string strCommand; + if (params.size() >= 1) { + strCommand = params[0].get_str(); + + // Backwards compatibility with legacy 'zeronode' super-command forwarder + if (strCommand == "start") strCommand = "local"; + if (strCommand == "start-alias") strCommand = "alias"; + if (strCommand == "start-all") strCommand = "all"; + if (strCommand == "start-many") strCommand = "many"; + if (strCommand == "start-missing") strCommand = "missing"; + if (strCommand == "start-disabled") strCommand = "disabled"; + } + + if (fHelp || params.size() < 2 || params.size() > 3 || + (params.size() == 2 && (strCommand != "local" && strCommand != "all" && strCommand != "many" && strCommand != "missing" && strCommand != "disabled")) || + (params.size() == 3 && strCommand != "alias")) + throw runtime_error( + "startzeronode \"local|all|many|missing|disabled|alias\" lockwallet ( \"alias\" )\n" + "\nAttempts to start one or more zeronode(s)\n" + + "\nArguments:\n" + "1. set (string, required) Specify which set of zeronode(s) to start.\n" + "2. lockwallet (boolean, required) Lock wallet after completion.\n" + "3. alias (string) Zeronode alias. Required if using 'alias' as the set.\n" + + "\nResult: (for 'local' set):\n" + "\"status\" (string) Zeronode status message\n" + + "\nResult: (for other sets):\n" + "{\n" + " \"overall\": \"xxxx\", (string) Overall status message\n" + " \"detail\": [\n" + " {\n" + " \"node\": \"xxxx\", (string) Node name or alias\n" + " \"result\": \"xxxx\", (string) 'success' or 'failed'\n" + " \"error\": \"xxxx\" (string) Error message, if failed\n" + " }\n" + " ,...\n" + " ]\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("startzeronode", "\"alias\" \"0\" \"my_zn\"") + HelpExampleRpc("startzeronode", "\"alias\" \"0\" \"my_zn\"")); + + if (!zeronodeSync.IsSynced()) + { + UniValue resultsObj(UniValue::VARR); + int successful = 0; + int failed = 0; + BOOST_FOREACH (CZeronodeConfig::CZeronodeEntry zne, zeronodeConfig.getEntries()) { + UniValue statusObj(UniValue::VOBJ); + statusObj.push_back(Pair("alias", zne.getAlias())); + statusObj.push_back(Pair("result", "failed")); + + failed++; + { + std::string error = "Zeronode is not synced, please wait. Current status: " + zeronodeSync.GetSyncStatus(); + statusObj.push_back(Pair("error", error)); + } + resultsObj.push_back(statusObj); + } + + UniValue returnObj(UniValue::VOBJ); + returnObj.push_back(Pair("overall", strprintf("Successfully started %d zeronodes, failed to start %d, total %d", successful, failed, successful + failed))); + returnObj.push_back(Pair("detail", resultsObj)); + + return returnObj; + } + + bool fLock = (params[1].get_str() == "true" ? true : false); + + if (strCommand == "local") { + if (!fZeroNode) throw runtime_error("you must set zeronode=1 in the configuration\n"); + + if (pwalletMain->IsLocked()) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); + + if (activeZeronode.status != ACTIVE_ZERONODE_STARTED) { + activeZeronode.status = ACTIVE_ZERONODE_INITIAL; // TODO: consider better way + activeZeronode.ManageStatus(); + if (fLock) + pwalletMain->Lock(); + } + + return activeZeronode.GetStatus(); + } + + if (strCommand == "all" || strCommand == "many" || strCommand == "missing" || strCommand == "disabled") { + if (pwalletMain->IsLocked()) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); + + if ((strCommand == "missing" || strCommand == "disabled") && + (zeronodeSync.RequestedZeronodeAssets <= ZERONODE_SYNC_LIST || + zeronodeSync.RequestedZeronodeAssets == ZERONODE_SYNC_FAILED)) { + throw runtime_error("You can't use this command until zeronode list is synced\n"); + } + + std::vector znEntries; + znEntries = zeronodeConfig.getEntries(); + + int successful = 0; + int failed = 0; + + UniValue resultsObj(UniValue::VARR); + + BOOST_FOREACH (CZeronodeConfig::CZeronodeEntry zne, zeronodeConfig.getEntries()) { + std::string errorMessage; + int nIndex; + if(!zne.castOutputIndex(nIndex)) + continue; + CTxIn vin = CTxIn(uint256S(zne.getTxHash()), uint32_t(nIndex)); + CZeronode* pzn = znodeman.Find(vin); + + if (pzn != NULL) { + if (strCommand == "missing") continue; + if (strCommand == "disabled" && pzn->IsEnabled()) continue; + } + + bool result = activeZeronode.Register(zne.getIp(), zne.getPrivKey(), zne.getTxHash(), zne.getOutputIndex(), errorMessage); + + UniValue statusObj(UniValue::VOBJ); + statusObj.push_back(Pair("alias", zne.getAlias())); + statusObj.push_back(Pair("result", result ? "success" : "failed")); + + if (result) { + successful++; + statusObj.push_back(Pair("error", "")); + } else { + failed++; + statusObj.push_back(Pair("error", errorMessage)); + } + + resultsObj.push_back(statusObj); + } + if (fLock) + pwalletMain->Lock(); + + UniValue returnObj(UniValue::VOBJ); + returnObj.push_back(Pair("overall", strprintf("Successfully started %d zeronodes, failed to start %d, total %d", successful, failed, successful + failed))); + returnObj.push_back(Pair("detail", resultsObj)); + + return returnObj; + } + + if (strCommand == "alias") { + std::string alias = params[2].get_str(); + + if (pwalletMain->IsLocked()) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); + + bool found = false; + int successful = 0; + int failed = 0; + + UniValue resultsObj(UniValue::VARR); + UniValue statusObj(UniValue::VOBJ); + statusObj.push_back(Pair("alias", alias)); + + BOOST_FOREACH (CZeronodeConfig::CZeronodeEntry zne, zeronodeConfig.getEntries()) { + if (zne.getAlias() == alias) { + found = true; + std::string errorMessage; + + bool result = activeZeronode.Register(zne.getIp(), zne.getPrivKey(), zne.getTxHash(), zne.getOutputIndex(), errorMessage); + + statusObj.push_back(Pair("result", result ? "successful" : "failed")); + + if (result) { + successful++; + statusObj.push_back(Pair("error", "")); + } else { + failed++; + statusObj.push_back(Pair("error", errorMessage)); + } + break; + } + } + + if (!found) { + failed++; + statusObj.push_back(Pair("result", "failed")); + statusObj.push_back(Pair("error", "could not find alias in config. Verify with list-conf.")); + } + + resultsObj.push_back(statusObj); + + if (fLock) + pwalletMain->Lock(); + + UniValue returnObj(UniValue::VOBJ); + returnObj.push_back(Pair("overall", strprintf("Successfully started %d zeronodes, failed to start %d, total %d", successful, failed, successful + failed))); + returnObj.push_back(Pair("detail", resultsObj)); + + return returnObj; + } + return NullUniValue; +} + +UniValue createzeronodekey (const UniValue& params, bool fHelp) +{ + if (fHelp || (params.size() != 0)) + throw runtime_error( + "createzeronodekey\n" + "\nCreate a new zeronode private key\n" + + "\nResult:\n" + "\"key\" (string) Zeronode private key\n" + "\nExamples:\n" + + HelpExampleCli("createzeronodekey", "") + HelpExampleRpc("createzeronodekey", "")); + + CKey secret; + secret.MakeNewKey(false); + + return EncodeSecret(secret); +} + +UniValue getzeronodeoutputs (const UniValue& params, bool fHelp) +{ + if (fHelp || (params.size() != 0)) + throw runtime_error( + "getzeronodeoutputs\n" + "\nPrint all zeronode transaction outputs\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"txhash\": \"xxxx\", (string) output transaction hash\n" + " \"outputidx\": n (numeric) output index number\n" + " }\n" + " ,...\n" + "]\n" + + "\nExamples:\n" + + HelpExampleCli("getzeronodeoutputs", "") + HelpExampleRpc("getzeronodeoutputs", "")); + + // Find possible candidates + vector possibleCoins = activeZeronode.SelectCoinsZeronode(); + + UniValue ret(UniValue::VARR); + BOOST_FOREACH (COutput& out, possibleCoins) { + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("txhash", out.tx->GetHash().ToString())); + obj.push_back(Pair("outputidx", out.i)); + ret.push_back(obj); + } + + return ret; +} + +UniValue listzeronodeconf (const UniValue& params, bool fHelp) +{ + std::string strFilter = ""; + + if (params.size() == 1) strFilter = params[0].get_str(); + + if (fHelp || (params.size() > 1)) + throw runtime_error( + "listzeronodeconf ( \"filter\" )\n" + "\nPrint zeronode.conf in JSON format\n" + + "\nArguments:\n" + "1. \"filter\" (string, optional) Filter search text. Partial match on alias, address, txHash, or status.\n" + + "\nResult:\n" + "[\n" + " {\n" + " \"alias\": \"xxxx\", (string) zeronode alias\n" + " \"address\": \"xxxx\", (string) zeronode IP address\n" + " \"privateKey\": \"xxxx\", (string) zeronode private key\n" + " \"txHash\": \"xxxx\", (string) transaction hash\n" + " \"outputIndex\": n, (numeric) transaction output index\n" + " \"status\": \"xxxx\" (string) zeronode status\n" + " }\n" + " ,...\n" + "]\n" + + "\nExamples:\n" + + HelpExampleCli("listzeronodeconf", "") + HelpExampleRpc("listzeronodeconf", "")); + + std::vector znEntries; + znEntries = zeronodeConfig.getEntries(); + + UniValue ret(UniValue::VARR); + + BOOST_FOREACH (CZeronodeConfig::CZeronodeEntry zne, zeronodeConfig.getEntries()) { + int nIndex; + if(!zne.castOutputIndex(nIndex)) + continue; + CTxIn vin = CTxIn(uint256S(zne.getTxHash()), uint32_t(nIndex)); + CZeronode* pzn = znodeman.Find(vin); + + std::string strStatus = pzn ? pzn->Status() : "MISSING"; + + if (strFilter != "" && zne.getAlias().find(strFilter) == string::npos && + zne.getIp().find(strFilter) == string::npos && + zne.getTxHash().find(strFilter) == string::npos && + strStatus.find(strFilter) == string::npos) continue; + + UniValue znObj(UniValue::VARR); + znObj.push_back(Pair("alias", zne.getAlias())); + znObj.push_back(Pair("address", zne.getIp())); + znObj.push_back(Pair("privateKey", zne.getPrivKey())); + znObj.push_back(Pair("txHash", zne.getTxHash())); + znObj.push_back(Pair("outputIndex", zne.getOutputIndex())); + znObj.push_back(Pair("status", strStatus)); + ret.push_back(znObj); + } + + return ret; +} + +UniValue getzeronodestatus (const UniValue& params, bool fHelp) +{ + if (fHelp || (params.size() != 0)) + throw runtime_error( + "getzeronodestatus\n" + "\nPrint zeronode status\n" + + "\nResult:\n" + "{\n" + " \"txhash\": \"xxxx\", (string) Collateral transaction hash\n" + " \"outputidx\": n, (numeric) Collateral transaction output index number\n" + " \"netaddr\": \"xxxx\", (string) Zeronode network address\n" + " \"addr\": \"xxxx\", (string) Zero address for zeronode payments\n" + " \"status\": \"xxxx\", (string) Zeronode status\n" + " \"message\": \"xxxx\" (string) Zeronode status message\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("getzeronodestatus", "") + HelpExampleRpc("getzeronodestatus", "")); + + if (!fZeroNode) throw runtime_error("This is not a zeronode"); + + CZeronode* pzn = znodeman.Find(activeZeronode.vin); + + if (pzn) { + UniValue znObj(UniValue::VARR); + znObj.push_back(Pair("txhash", activeZeronode.vin.prevout.hash.ToString())); + znObj.push_back(Pair("outputidx", (uint64_t)activeZeronode.vin.prevout.n)); + znObj.push_back(Pair("netaddr", activeZeronode.service.ToString())); + znObj.push_back(Pair("addr", EncodeDestination(pzn->pubKeyCollateralAddress.GetID()))); + znObj.push_back(Pair("status", activeZeronode.status)); + znObj.push_back(Pair("message", activeZeronode.GetStatus())); + return znObj; + } + throw runtime_error("Zeronode not found in the list of available zeronodes. Current status: " + + activeZeronode.GetStatus()); +} + +UniValue getzeronodewinners (const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() > 3) + throw runtime_error( + "getzeronodewinners ( blocks \"filter\" )\n" + "\nPrint the zeronode winners for the last n blocks\n" + + "\nArguments:\n" + "1. blocks (numeric, optional) Number of previous blocks to show (default: 10)\n" + "2. filter (string, optional) Search filter matching MN address\n" + + "\nResult (single winner):\n" + "[\n" + " {\n" + " \"nHeight\": n, (numeric) block height\n" + " \"winner\": {\n" + " \"address\": \"xxxx\", (string) Zero MN Address\n" + " \"nVotes\": n, (numeric) Number of votes for winner\n" + " }\n" + " }\n" + " ,...\n" + "]\n" + + "\nResult (multiple winners):\n" + "[\n" + " {\n" + " \"nHeight\": n, (numeric) block height\n" + " \"winner\": [\n" + " {\n" + " \"address\": \"xxxx\", (string) Zero MN Address\n" + " \"nVotes\": n, (numeric) Number of votes for winner\n" + " }\n" + " ,...\n" + " ]\n" + " }\n" + " ,...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("getzeronodewinners", "") + HelpExampleRpc("getzeronodewinners", "")); + + int nHeight; + { + LOCK(cs_main); + CBlockIndex* pindex = chainActive.Tip(); + if(!pindex) return 0; + nHeight = pindex->nHeight; + } + + int nLast = 10; + std::string strFilter = ""; + + if (params.size() >= 1) + nLast = atoi(params[0].get_str()); + + if (params.size() == 2) + strFilter = params[1].get_str(); + + UniValue ret(UniValue::VARR); + + for (int i = nHeight - nLast; i < nHeight + 20; i++) { + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("nHeight", i)); + + std::string strPayment = GetRequiredPaymentsString(i); + if (strFilter != "" && strPayment.find(strFilter) == std::string::npos) continue; + + if (strPayment.find(',') != std::string::npos) { + UniValue winner(UniValue::VARR); + boost::char_separator sep(","); + boost::tokenizer< boost::char_separator > tokens(strPayment, sep); + BOOST_FOREACH (const string& t, tokens) { + UniValue addr(UniValue::VOBJ); + std::size_t pos = t.find(":"); + std::string strAddress = t.substr(0,pos); + uint64_t nVotes = atoi(t.substr(pos+1)); + addr.push_back(Pair("address", strAddress)); + addr.push_back(Pair("nVotes", nVotes)); + winner.push_back(addr); + } + obj.push_back(Pair("winner", winner)); + } else if (strPayment.find("Unknown") == std::string::npos) { + UniValue winner(UniValue::VOBJ); + std::size_t pos = strPayment.find(":"); + std::string strAddress = strPayment.substr(0,pos); + uint64_t nVotes = atoi(strPayment.substr(pos+1)); + winner.push_back(Pair("address", strAddress)); + winner.push_back(Pair("nVotes", nVotes)); + obj.push_back(Pair("winner", winner)); + } else { + UniValue winner(UniValue::VOBJ); + winner.push_back(Pair("address", strPayment)); + winner.push_back(Pair("nVotes", 0)); + obj.push_back(Pair("winner", winner)); + } + + ret.push_back(obj); + } + + return ret; +} + +UniValue getzeronodescores (const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "getzeronodescores ( blocks )\n" + "\nPrint list of winning zeronode by score\n" + + "\nArguments:\n" + "1. blocks (numeric, optional) Show the last n blocks (default 10)\n" + + "\nResult:\n" + "{\n" + " xxxx: \"xxxx\" (numeric : string) Block height : Zeronode hash\n" + " ,...\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("getzeronodescores", "") + HelpExampleRpc("getzeronodescores", "")); + + int nLast = 10; + + if (params.size() == 1) { + try { + nLast = std::stoi(params[0].get_str()); + } catch (const boost::bad_lexical_cast &) { + throw runtime_error("Exception on param 2"); + } + } + UniValue obj(UniValue::VOBJ); + + int nHeight = chainActive.Tip()->nHeight - nLast; + + uint256 blockHash; + if(!GetBlockHash(blockHash, nHeight - 100)) { + return NullUniValue; + } + + std::vector vZeronodes = znodeman.GetFullZeronodeVector(); + for (int height = nHeight; height < chainActive.Tip()->nHeight + 20; height++) { + arith_uint256 nHigh = 0; + CZeronode* pBestZeronode = NULL; + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + arith_uint256 n = zn.CalculateScore(blockHash); + if (n > nHigh) { + nHigh = n; + pBestZeronode = &zn; + } + } + if (pBestZeronode) + obj.push_back(Pair(strprintf("%d", height), pBestZeronode->vin.prevout.hash.ToString().c_str())); + } + + return obj; +} + +UniValue znsync(const UniValue& params, bool fHelp) +{ + std::string strMode; + if (params.size() == 1) + strMode = params[0].get_str(); + + if (fHelp || params.size() != 1 || (strMode != "status" && strMode != "reset")) { + throw runtime_error( + "znsync \"status|reset\"\n" + "\nReturns the sync status or resets sync.\n" + + "\nArguments:\n" + "1. \"mode\" (string, required) either 'status' or 'reset'\n" + + "\nResult ('status' mode):\n" + "{\n" + " \"IsBlockchainSynced\": true|false, (boolean) 'true' if blockchain is synced\n" + " \"lastZeronodeList\": xxxx, (numeric) Timestamp of last MN list message\n" + " \"lastZeronodeWinner\": xxxx, (numeric) Timestamp of last MN winner message\n" + " \"lastBudgetItem\": xxxx, (numeric) Timestamp of last MN budget message\n" + " \"lastFailure\": xxxx, (numeric) Timestamp of last failed sync\n" + " \"nCountFailures\": n, (numeric) Number of failed syncs (total)\n" + " \"sumZeronodeList\": n, (numeric) Number of MN list messages (total)\n" + " \"sumZeronodeWinner\": n, (numeric) Number of MN winner messages (total)\n" + " \"sumBudgetItemProp\": n, (numeric) Number of MN budget messages (total)\n" + " \"sumBudgetItemFin\": n, (numeric) Number of MN budget finalization messages (total)\n" + " \"countZeronodeList\": n, (numeric) Number of MN list messages (local)\n" + " \"countZeronodeWinner\": n, (numeric) Number of MN winner messages (local)\n" + " \"countBudgetItemProp\": n, (numeric) Number of MN budget messages (local)\n" + " \"countBudgetItemFin\": n, (numeric) Number of MN budget finalization messages (local)\n" + " \"RequestedZeronodeAssets\": n, (numeric) Status code of last sync phase\n" + " \"RequestedZeronodeAttempt\": n, (numeric) Status code of last sync attempt\n" + "}\n" + + "\nResult ('reset' mode):\n" + "\"status\" (string) 'success'\n" + "\nExamples:\n" + + HelpExampleCli("znsync", "\"status\"") + HelpExampleRpc("znsync", "\"status\"")); + } + + if (strMode == "status") { + UniValue obj(UniValue::VOBJ); + + obj.push_back(Pair("IsBlockchainSynced", zeronodeSync.IsBlockchainSynced())); + obj.push_back(Pair("lastZeronodeList", zeronodeSync.lastZeronodeList)); + obj.push_back(Pair("lastZeronodeWinner", zeronodeSync.lastZeronodeWinner)); + obj.push_back(Pair("lastBudgetItem", zeronodeSync.lastBudgetItem)); + obj.push_back(Pair("lastFailure", zeronodeSync.lastFailure)); + obj.push_back(Pair("nCountFailures", zeronodeSync.nCountFailures)); + obj.push_back(Pair("sumZeronodeList", zeronodeSync.sumZeronodeList)); + obj.push_back(Pair("sumZeronodeWinner", zeronodeSync.sumZeronodeWinner)); + obj.push_back(Pair("sumBudgetItemProp", zeronodeSync.sumBudgetItemProp)); + obj.push_back(Pair("sumBudgetItemFin", zeronodeSync.sumBudgetItemFin)); + obj.push_back(Pair("countZeronodeList", zeronodeSync.countZeronodeList)); + obj.push_back(Pair("countZeronodeWinner", zeronodeSync.countZeronodeWinner)); + obj.push_back(Pair("countBudgetItemProp", zeronodeSync.countBudgetItemProp)); + obj.push_back(Pair("countBudgetItemFin", zeronodeSync.countBudgetItemFin)); + obj.push_back(Pair("RequestedZeronodeAssets", zeronodeSync.RequestedZeronodeAssets)); + obj.push_back(Pair("RequestedZeronodeAttempt", zeronodeSync.RequestedZeronodeAttempt)); + + return obj; + } + + if (strMode == "reset") { + zeronodeSync.Reset(); + return "success"; + } + return "failure"; +} + +// This command is retained for backwards compatibility, but is depreciated. +// Future removal of this command is planned to keep things clean. +UniValue zeronode(const UniValue& params, bool fHelp) +{ + string strCommand; + if (params.size() >= 1) + strCommand = params[0].get_str(); + + if (fHelp || + (strCommand != "start" && strCommand != "start-alias" && strCommand != "start-many" && strCommand != "start-all" && strCommand != "start-missing" && + strCommand != "start-disabled" && strCommand != "list" && strCommand != "list-conf" && strCommand != "count" && strCommand != "enforce" && + strCommand != "debug" && strCommand != "current" && strCommand != "winners" && strCommand != "genkey" && strCommand != "connect" && + strCommand != "outputs" && strCommand != "status" && strCommand != "calcscore")) + throw runtime_error( + "zeronode \"command\"...\n" + "\nSet of commands to execute zeronode related actions\n" + "This command is depreciated, please see individual command documentation for future reference\n\n" + + "\nArguments:\n" + "1. \"command\" (string or set of strings, required) The command to execute\n" + + "\nAvailable commands:\n" + " count - Print count information of all known zeronodes\n" + " connect - Attempts to connect to specified zeronode address\n" + " current - Print info on current zeronode winner\n" + " debug - Print zeronode status\n" + " genkey - Generate new zeronodeprivkey\n" + " outputs - Print zeronode compatible outputs\n" + " start - Start zeronode configured in zero.conf\n" + " start-alias - Start single zeronode by assigned alias configured in zeronode.conf\n" + " start- - Start zeronodes configured in zeronode.conf (: 'all', 'missing', 'disabled')\n" + " status - Print zeronode status information\n" + " list - Print list of all known zeronodes (see zeronodelist for more info)\n" + " list-conf - Print zeronode.conf in JSON format\n" + " winners - Print list of zeronode winners\n" + " calcscore - Print list of winning zeronode by score\n"); + + if (strCommand == "list") { + UniValue newParams(UniValue::VARR); + + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + + return listzeronodes(newParams, fHelp); + } + + if (strCommand == "connect") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return zeronodeconnect(newParams, fHelp); + } + + if (strCommand == "count") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return getzeronodecount(newParams, fHelp); + } + + if (strCommand == "current") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return zeronodecurrent(newParams, fHelp); + } + + if (strCommand == "debug") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return zeronodedebug(newParams, fHelp); + } + + if (strCommand == "start" || strCommand == "start-alias" || strCommand == "start-many" || strCommand == "start-all" || strCommand == "start-missing" || strCommand == "start-disabled") { + return startzeronode(params, fHelp); + } + + if (strCommand == "genkey") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return createzeronodekey(newParams, fHelp); + } + + if (strCommand == "list-conf") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return listzeronodeconf(newParams, fHelp); + } + + if (strCommand == "outputs") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return getzeronodeoutputs(newParams, fHelp); + } + + if (strCommand == "status") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return getzeronodestatus(newParams, fHelp); + } + + if (strCommand == "winners") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return getzeronodewinners(newParams, fHelp); + } + + if (strCommand == "calcscore") { + UniValue newParams(UniValue::VARR); + for (unsigned int i = 1; i < params.size(); i++) { + newParams.push_back(params[i]); + } + return getzeronodescores(newParams, fHelp); + } + + return NullUniValue; +} + +static const CRPCCommand commands[] = +{ // category name actor (function) okSafeMode + // --------------------- ------------------------ ----------------------- ---------- + /* MN features */ + {"zeronode", "getzeronodecount", &getzeronodecount, true}, + {"zeronode", "zeronodeconnect", &zeronodeconnect, true}, + {"zeronode", "zeronodecurrent", &zeronodecurrent, true}, + {"zeronode", "zeronodedebug", &zeronodedebug, true}, + {"zeronode", "createzeronodekey", &createzeronodekey, true}, + {"zeronode", "getzeronodeoutputs", &getzeronodeoutputs, true}, + {"zeronode", "startzeronode", &startzeronode, true}, + {"zeronode", "startalias", &startalias, true}, + {"zeronode", "getzeronodestatus", &getzeronodestatus, true}, + {"zeronode", "listzeronodes", &listzeronodes, true}, + {"zeronode", "listzeronodeconf", &listzeronodeconf, true}, + {"zeronode", "getzeronodewinners", &getzeronodewinners, true}, + {"zeronode", "getzeronodescores", &getzeronodescores, true}, + {"zeronode", "zeronode", &zeronode, true}, + {"zeronode", "znsync", &znsync, true}, + +}; + +void RegisterZeronodeRPCCommands(CRPCTable &tableRPC) +{ + for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) + tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]); +} diff --git a/src/rpcserver.h b/src/rpcserver.h deleted file mode 100644 index 5691fa14da8..00000000000 --- a/src/rpcserver.h +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2014 The Bitcoin Core developers -// Distributed under the MIT software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#ifndef BITCOIN_RPCSERVER_H -#define BITCOIN_RPCSERVER_H - -#include "amount.h" -#include "rpcprotocol.h" -#include "uint256.h" - -#include -#include -#include -#include -#include - -#include - -#include - -class AsyncRPCQueue; -class CRPCCommand; - -namespace RPCServer -{ - void OnStarted(boost::function slot); - void OnStopped(boost::function slot); - void OnPreCommand(boost::function slot); - void OnPostCommand(boost::function slot); -} - -class CBlockIndex; -class CNetAddr; - -class JSONRequest -{ -public: - UniValue id; - std::string strMethod; - UniValue params; - - JSONRequest() { id = NullUniValue; } - void parse(const UniValue& valRequest); -}; - -/** Query whether RPC is running */ -bool IsRPCRunning(); - -/** Get the async queue*/ -std::shared_ptr getAsyncRPCQueue(); - - -/** - * Set the RPC warmup status. When this is done, all RPC calls will error out - * immediately with RPC_IN_WARMUP. - */ -void SetRPCWarmupStatus(const std::string& newStatus); -/* Mark warmup as done. RPC calls will be processed from now on. */ -void SetRPCWarmupFinished(); - -/* returns the current warmup state. */ -bool RPCIsInWarmup(std::string *statusOut); - -/** - * Type-check arguments; throws JSONRPCError if wrong type given. Does not check that - * the right number of arguments are passed, just that any passed are the correct type. - * Use like: RPCTypeCheck(params, boost::assign::list_of(str_type)(int_type)(obj_type)); - */ -void RPCTypeCheck(const UniValue& params, - const std::list& typesExpected, bool fAllowNull=false); - -/* - Check for expected keys/value types in an Object. - Use like: RPCTypeCheckObj(object, boost::assign::map_list_of("name", str_type)("value", int_type)); -*/ -void RPCTypeCheckObj(const UniValue& o, - const std::map& typesExpected, bool fAllowNull=false); - -/** Opaque base class for timers returned by NewTimerFunc. - * This provides no methods at the moment, but makes sure that delete - * cleans up the whole state. - */ -class RPCTimerBase -{ -public: - virtual ~RPCTimerBase() {} -}; - -/** - * RPC timer "driver". - */ -class RPCTimerInterface -{ -public: - virtual ~RPCTimerInterface() {} - /** Implementation name */ - virtual const char *Name() = 0; - /** Factory function for timers. - * RPC will call the function to create a timer that will call func in *millis* milliseconds. - * @note As the RPC mechanism is backend-neutral, it can use different implementations of timers. - * This is needed to cope with the case in which there is no HTTP server, but - * only GUI RPC console, and to break the dependency of rpcserver on httprpc. - */ - virtual RPCTimerBase* NewTimer(boost::function& func, int64_t millis) = 0; -}; - -/** Register factory function for timers */ -void RPCRegisterTimerInterface(RPCTimerInterface *iface); -/** Unregister factory function for timers */ -void RPCUnregisterTimerInterface(RPCTimerInterface *iface); - -/** - * Run func nSeconds from now. - * Overrides previous timer (if any). - */ -void RPCRunLater(const std::string& name, boost::function func, int64_t nSeconds); - -typedef UniValue(*rpcfn_type)(const UniValue& params, bool fHelp); - -class CRPCCommand -{ -public: - std::string category; - std::string name; - rpcfn_type actor; - bool okSafeMode; -}; - -/** - * Bitcoin RPC command dispatcher. - */ -class CRPCTable -{ -private: - std::map mapCommands; -public: - CRPCTable(); - const CRPCCommand* operator[](const std::string& name) const; - std::string help(const std::string& name) const; - - /** - * Execute a method. - * @param method Method to execute - * @param params UniValue Array of arguments (JSON objects) - * @returns Result of the call. - * @throws an exception (UniValue) when an error happens. - */ - UniValue execute(const std::string &method, const UniValue ¶ms) const; -}; - -extern const CRPCTable tableRPC; - -/** - * Utilities: convert hex-encoded Values - * (throws error if not hex). - */ -extern uint256 ParseHashV(const UniValue& v, std::string strName); -extern uint256 ParseHashO(const UniValue& o, std::string strKey); -extern std::vector ParseHexV(const UniValue& v, std::string strName); -extern std::vector ParseHexO(const UniValue& o, std::string strKey); - -extern int64_t nWalletUnlockTime; -extern CAmount AmountFromValue(const UniValue& value); -extern UniValue ValueFromAmount(const CAmount& amount); -extern double GetDifficulty(const CBlockIndex* blockindex = NULL); -extern double GetNetworkDifficulty(const CBlockIndex* blockindex = NULL); -extern std::string HelpRequiringPassphrase(); -extern std::string HelpExampleCli(const std::string& methodname, const std::string& args); -extern std::string HelpExampleRpc(const std::string& methodname, const std::string& args); - -extern void EnsureWalletIsUnlocked(); - -extern UniValue getconnectioncount(const UniValue& params, bool fHelp); // in rpcnet.cpp -extern UniValue getaddressmempool(const UniValue& params, bool fHelp); -extern UniValue getaddressutxos(const UniValue& params, bool fHelp); -extern UniValue getaddressdeltas(const UniValue& params, bool fHelp); -extern UniValue getaddresstxids(const UniValue& params, bool fHelp); -extern UniValue getaddressbalance(const UniValue& params, bool fHelp); - -extern UniValue getpeerinfo(const UniValue& params, bool fHelp); -extern UniValue ping(const UniValue& params, bool fHelp); -extern UniValue addnode(const UniValue& params, bool fHelp); -extern UniValue disconnectnode(const UniValue& params, bool fHelp); -extern UniValue getaddednodeinfo(const UniValue& params, bool fHelp); -extern UniValue getnettotals(const UniValue& params, bool fHelp); -extern UniValue setban(const UniValue& params, bool fHelp); -extern UniValue listbanned(const UniValue& params, bool fHelp); -extern UniValue clearbanned(const UniValue& params, bool fHelp); - -extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp -extern UniValue importprivkey(const UniValue& params, bool fHelp); -extern UniValue importaddress(const UniValue& params, bool fHelp); -extern UniValue dumpwallet(const UniValue& params, bool fHelp); -extern UniValue importwallet(const UniValue& params, bool fHelp); - -extern UniValue getgenerate(const UniValue& params, bool fHelp); // in rpcmining.cpp -extern UniValue setgenerate(const UniValue& params, bool fHelp); -extern UniValue generate(const UniValue& params, bool fHelp); -extern UniValue getlocalsolps(const UniValue& params, bool fHelp); -extern UniValue getnetworksolps(const UniValue& params, bool fHelp); -extern UniValue getnetworkhashps(const UniValue& params, bool fHelp); -extern UniValue getmininginfo(const UniValue& params, bool fHelp); -extern UniValue prioritisetransaction(const UniValue& params, bool fHelp); -extern UniValue getblocktemplate(const UniValue& params, bool fHelp); -extern UniValue submitblock(const UniValue& params, bool fHelp); -extern UniValue estimatefee(const UniValue& params, bool fHelp); -extern UniValue estimatepriority(const UniValue& params, bool fHelp); - -extern UniValue getnewaddress(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue getaccountaddress(const UniValue& params, bool fHelp); -extern UniValue getrawchangeaddress(const UniValue& params, bool fHelp); -extern UniValue setaccount(const UniValue& params, bool fHelp); -extern UniValue getaccount(const UniValue& params, bool fHelp); -extern UniValue getaddressesbyaccount(const UniValue& params, bool fHelp); -extern UniValue sendtoaddress(const UniValue& params, bool fHelp); -extern UniValue signmessage(const UniValue& params, bool fHelp); -extern UniValue verifymessage(const UniValue& params, bool fHelp); -extern UniValue getreceivedbyaddress(const UniValue& params, bool fHelp); -extern UniValue getreceivedbyaccount(const UniValue& params, bool fHelp); -extern UniValue getbalance(const UniValue& params, bool fHelp); -extern UniValue getunconfirmedbalance(const UniValue& params, bool fHelp); -extern UniValue movecmd(const UniValue& params, bool fHelp); -extern UniValue sendfrom(const UniValue& params, bool fHelp); -extern UniValue sendmany(const UniValue& params, bool fHelp); -extern UniValue addmultisigaddress(const UniValue& params, bool fHelp); -extern UniValue createmultisig(const UniValue& params, bool fHelp); -extern UniValue listreceivedbyaddress(const UniValue& params, bool fHelp); -extern UniValue listreceivedbyaccount(const UniValue& params, bool fHelp); -extern UniValue listtransactions(const UniValue& params, bool fHelp); -extern UniValue listaddressgroupings(const UniValue& params, bool fHelp); -extern UniValue listaccounts(const UniValue& params, bool fHelp); -extern UniValue listsinceblock(const UniValue& params, bool fHelp); -extern UniValue gettransaction(const UniValue& params, bool fHelp); -extern UniValue backupwallet(const UniValue& params, bool fHelp); -extern UniValue keypoolrefill(const UniValue& params, bool fHelp); -extern UniValue walletpassphrase(const UniValue& params, bool fHelp); -extern UniValue walletpassphrasechange(const UniValue& params, bool fHelp); -extern UniValue walletlock(const UniValue& params, bool fHelp); -extern UniValue encryptwallet(const UniValue& params, bool fHelp); -extern UniValue validateaddress(const UniValue& params, bool fHelp); -extern UniValue getinfo(const UniValue& params, bool fHelp); -extern UniValue getwalletinfo(const UniValue& params, bool fHelp); -extern UniValue getblockchaininfo(const UniValue& params, bool fHelp); -extern UniValue getnetworkinfo(const UniValue& params, bool fHelp); -extern UniValue getdeprecationinfo(const UniValue& params, bool fHelp); -extern UniValue setmocktime(const UniValue& params, bool fHelp); -extern UniValue resendwallettransactions(const UniValue& params, bool fHelp); -extern UniValue zc_benchmark(const UniValue& params, bool fHelp); -extern UniValue zc_raw_keygen(const UniValue& params, bool fHelp); -extern UniValue zc_raw_joinsplit(const UniValue& params, bool fHelp); -extern UniValue zc_raw_receive(const UniValue& params, bool fHelp); -extern UniValue zc_sample_joinsplit(const UniValue& params, bool fHelp); - -extern UniValue getrawtransaction(const UniValue& params, bool fHelp); // in rcprawtransaction.cpp -extern UniValue listunspent(const UniValue& params, bool fHelp); -extern UniValue lockunspent(const UniValue& params, bool fHelp); -extern UniValue listlockunspent(const UniValue& params, bool fHelp); -extern UniValue createrawtransaction(const UniValue& params, bool fHelp); -extern UniValue decoderawtransaction(const UniValue& params, bool fHelp); -extern UniValue decodescript(const UniValue& params, bool fHelp); -extern UniValue fundrawtransaction(const UniValue& params, bool fHelp); -extern UniValue signrawtransaction(const UniValue& params, bool fHelp); -extern UniValue sendrawtransaction(const UniValue& params, bool fHelp); -extern UniValue gettxoutproof(const UniValue& params, bool fHelp); -extern UniValue verifytxoutproof(const UniValue& params, bool fHelp); - -extern UniValue getblockcount(const UniValue& params, bool fHelp); // in rpcblockchain.cpp -extern UniValue getbestblockhash(const UniValue& params, bool fHelp); -extern UniValue getdifficulty(const UniValue& params, bool fHelp); -extern UniValue settxfee(const UniValue& params, bool fHelp); -extern UniValue getmempoolinfo(const UniValue& params, bool fHelp); -extern UniValue getrawmempool(const UniValue& params, bool fHelp); -extern UniValue getblockhashes(const UniValue& params, bool fHelp); -extern UniValue getblockdeltas(const UniValue& params, bool fHelp); -extern UniValue getblockhash(const UniValue& params, bool fHelp); -extern UniValue getblockheader(const UniValue& params, bool fHelp); -extern UniValue getblock(const UniValue& params, bool fHelp); -extern UniValue gettxoutsetinfo(const UniValue& params, bool fHelp); -extern UniValue gettxout(const UniValue& params, bool fHelp); -extern UniValue verifychain(const UniValue& params, bool fHelp); -extern UniValue getchaintips(const UniValue& params, bool fHelp); -extern UniValue invalidateblock(const UniValue& params, bool fHelp); -extern UniValue reconsiderblock(const UniValue& params, bool fHelp); -extern UniValue getspentinfo(const UniValue& params, bool fHelp); - -extern UniValue getblocksubsidy(const UniValue& params, bool fHelp); - -extern UniValue z_exportkey(const UniValue& params, bool fHelp); // in rpcdump.cpp -extern UniValue z_importkey(const UniValue& params, bool fHelp); // in rpcdump.cpp -extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp); // in rpcdump.cpp -extern UniValue z_importviewingkey(const UniValue& params, bool fHelp); // in rpcdump.cpp -extern UniValue z_getnewaddress(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_listaddresses(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_exportwallet(const UniValue& params, bool fHelp); // in rpcdump.cpp -extern UniValue z_importwallet(const UniValue& params, bool fHelp); // in rpcdump.cpp -extern UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_listunspent(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_getbalance(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_gettotalbalance(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_mergetoaddress(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_sendmany(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_shieldcoinbase(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_getoperationstatus(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_getoperationresult(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_listoperationids(const UniValue& params, bool fHelp); // in rpcwallet.cpp -extern UniValue z_validateaddress(const UniValue& params, bool fHelp); // in rpcmisc.cpp -extern UniValue z_getpaymentdisclosure(const UniValue& params, bool fHelp); // in rpcdisclosure.cpp -extern UniValue z_validatepaymentdisclosure(const UniValue ¶ms, bool fHelp); // in rpcdisclosure.cpp - -bool StartRPC(); -void InterruptRPC(); -void StopRPC(); -std::string JSONRPCExecBatch(const UniValue& vReq); - -#endif // BITCOIN_RPCSERVER_H diff --git a/src/script/script.cpp b/src/script/script.cpp index 6ae1250eb56..5532837b1e3 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -212,6 +212,30 @@ bool CScript::IsPayToPublicKeyHash() const (*this)[24] == OP_CHECKSIG); } +bool CScript::IsNormalPaymentScript() const +{ + if(this->size() != 25) return false; + + std::string str; + opcodetype opcode; + const_iterator pc = begin(); + int i = 0; + while (pc < end()) + { + GetOp(pc, opcode); + + if( i == 0 && opcode != OP_DUP) return false; + else if(i == 1 && opcode != OP_HASH160) return false; + else if(i == 3 && opcode != OP_EQUALVERIFY) return false; + else if(i == 4 && opcode != OP_CHECKSIG) return false; + else if(i == 5) return false; + + i++; + } + + return true; +} + bool CScript::IsPayToScriptHash() const { // Extra-fast test for pay-to-script-hash CScripts: diff --git a/src/script/script.h b/src/script/script.h index 1eab27ab07a..9874c41f9d1 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -567,6 +567,7 @@ class CScript : public CScriptBase bool IsPayToPublicKeyHash() const; + bool IsNormalPaymentScript() const; bool IsPayToScriptHash() const; /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ diff --git a/src/sendalert.cpp b/src/sendalert.cpp index cdc4d58af6c..33bc3dd4f30 100644 --- a/src/sendalert.cpp +++ b/src/sendalert.cpp @@ -94,7 +94,7 @@ void ThreadSendAlert() alert.strRPCError = alert.strStatusBar; // Set specific client version/versions here. If setSubVer is empty, no filtering on subver is done: - const std::vector useragents = {"Isfjorden"}; + const std::vector useragents = {"Isfjorden","Ny-Alesund"}; BOOST_FOREACH(const std::string& useragent, useragents) { alert.setSubVer.insert(std::string("/"+useragent+":2.0.1/")); diff --git a/src/serialize.h b/src/serialize.h index 77d8991eee6..24a2cf9e79e 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -27,6 +27,8 @@ #include "prevector.h" +class CScript; + static const unsigned int MAX_SIZE = 0x02000000; /** @@ -533,6 +535,12 @@ template void Unserialize_impl(Stream& template void Unserialize_impl(Stream& is, std::vector& v, const V&); template inline void Unserialize(Stream& is, std::vector& v); +/** + * others derived from vector + */ +template void Serialize(Stream& os, const CScript& v); +template void Unserialize(Stream& is, CScript& v); + /** * optional */ @@ -762,6 +770,20 @@ inline void Unserialize(Stream& is, std::vector& v) Unserialize_impl(is, v, T()); } +/** + * others derived from vector + */ +template +void Serialize(Stream& os, const CScript& v) +{ + Serialize(os, (const std::vector&)v); +} + +template +void Unserialize(Stream& is, CScript& v) +{ + Unserialize(is, (std::vector&)v); +} /** diff --git a/src/util.cpp b/src/util.cpp index 046a77cc304..0ba69802bf5 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -99,6 +99,18 @@ namespace boost { using namespace std; +// Zeronode +// SwiftX +bool fEnableSwiftTX = true; +int nSwiftTXDepth = 5; +bool fZeroNode = false; +string strZeroNodePrivKey = ""; +string strZeroNodeAddr = ""; +bool fLiteMode = false; +/** All denominations used by obfuscation */ +std::vector obfuScationDenominations; +string strBudgetMode = ""; + map mapArgs; map > mapMultiArgs; bool fDebug = false; @@ -604,6 +616,13 @@ boost::filesystem::path GetConfigFile() return pathConfigFile; } +boost::filesystem::path GetZeronodeConfigFile() +{ + boost::filesystem::path pathConfigFile(GetArg("-znconf", "zeronode.conf")); + if (!pathConfigFile.is_complete()) pathConfigFile = GetDataDir() / pathConfigFile; + return pathConfigFile; +} + void ReadConfigFile(map& mapSettingsRet, map >& mapMultiSettingsRet) { diff --git a/src/util.h b/src/util.h index f957a12f79d..31708d9ec98 100644 --- a/src/util.h +++ b/src/util.h @@ -33,6 +33,11 @@ static const bool DEFAULT_LOGTIMEMICROS = false; static const bool DEFAULT_LOGIPS = false; static const bool DEFAULT_LOGTIMESTAMPS = true; +extern bool fEnableSwiftTX; +extern int nSwiftTXDepth; +extern bool fZeroNode; +extern bool fLiteMode; + /** Signals for translation. */ class CTranslationInterface { @@ -41,6 +46,11 @@ class CTranslationInterface boost::signals2::signal Translate; }; +//Zeronode only features +extern std::string strZeroNodeAddr; +extern std::vector obfuScationDenominations; +extern std::string strBudgetMode; + extern std::map mapArgs; extern std::map > mapMultiArgs; extern bool fDebug; @@ -126,6 +136,7 @@ boost::filesystem::path GetDefaultDataDir(); const boost::filesystem::path &GetDataDir(bool fNetSpecific = true); void ClearDatadirCache(); boost::filesystem::path GetConfigFile(); +boost::filesystem::path GetZeronodeConfigFile(); #ifndef WIN32 boost::filesystem::path GetPidFile(); void CreatePidFile(const boost::filesystem::path &path, pid_t pid); diff --git a/src/validationinterface.h b/src/validationinterface.h index 7b02bd9da4a..3aed5e43b04 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -38,7 +38,7 @@ class CValidationInterface { virtual void EraseFromWallet(const uint256 &hash) {} virtual void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, SproutMerkleTree sproutTree, SaplingMerkleTree saplingTree, bool added) {} virtual void SetBestChain(const CBlockLocator &locator) {} - virtual void UpdatedTransaction(const uint256 &hash) {} + virtual bool UpdatedTransaction(const uint256 &hash) {return false;} virtual void Inventory(const uint256 &hash) {} virtual void ResendWalletTransactions(int64_t nBestBlockTime) {} virtual void BlockChecked(const CBlock&, const CValidationState&) {} diff --git a/src/version.h b/src/version.h index 72ddac446a6..601b9174bf3 100644 --- a/src/version.h +++ b/src/version.h @@ -9,7 +9,7 @@ * network protocol versioning */ -static const int PROTOCOL_VERSION = 170007; +static const int PROTOCOL_VERSION = 170008; //! initial proto version, to be increased after version/verack negotiation static const int INIT_PROTO_VERSION = 209; @@ -18,7 +18,9 @@ static const int INIT_PROTO_VERSION = 209; static const int GETHEADERS_VERSION = 31800; //! disconnect from peers older than this proto version -static const int MIN_PEER_PROTO_VERSION = 170002; +static const int MIN_PEER_PROTO_VERSION = 170007; +//! disconnect from peers older than this proto version +static const int MIN_PEER_PROTO_VERSION_ENFORCEMENT = 170008; //! nTime field added to CAddress, starting with this version; //! if possible, avoid requesting addresses nodes older than this diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index 5462b6e447b..3ced44d65ee 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -1024,9 +1024,9 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) { t_inputs_.push_back(utxo); } - // sort in ascending order, so smaller utxos appear first + // sort in ascending order, so larger utxos appear first std::sort(t_inputs_.begin(), t_inputs_.end(), [](SendManyInputUTXO i, SendManyInputUTXO j) -> bool { - return ( std::get<2>(i) < std::get<2>(j)); + return ( std::get<2>(i) > std::get<2>(j)); }); return t_inputs_.size() > 0; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f5c4d910cdb..dfcaaad75f2 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3626,6 +3626,7 @@ UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedO // For now though, we assume we use one joinsplit per zaddr output (and the second output note is change). // We reduce the result by 1 to ensure there is room for non-joinsplit CTransaction data. #define Z_SENDMANY_MAX_ZADDR_OUTPUTS_BEFORE_SAPLING ((MAX_TX_SIZE_BEFORE_SAPLING / V3_JS_DESCRIPTION_SIZE) - 1) +#define Z_SENDMANY_MAX_ZADDR_OUTPUTS_COSMOS ((MAX_TX_SIZE_COSMOS / V3_JS_DESCRIPTION_SIZE) - 1) // transaction.h comment: spending taddr output requires CTxIn >= 148 bytes and typical taddr txout is 34 bytes #define CTXIN_SPEND_DUST_SIZE 148 @@ -3806,6 +3807,10 @@ UniValue z_sendmany(const UniValue& params, bool fHelp) } } + if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_COSMOS)) { + max_tx_size = MAX_TX_SIZE_COSMOS; + } + // If Sapling is not active, do not allow sending from or sending to Sapling addresses. if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { if (fromSapling || containsSaplingOutput) { @@ -3936,7 +3941,7 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp) "\nby the caller. If the limit parameter is set to zero, and Overwinter is not yet active, the -mempooltxinputlimit" "\noption will determine the number of uxtos. Any limit is constrained by the consensus rule defining a maximum" "\ntransaction size of " - + strprintf("%d bytes before Sapling, and %d bytes once Sapling activates.", MAX_TX_SIZE_BEFORE_SAPLING, MAX_TX_SIZE_AFTER_SAPLING) + + strprintf("%d bytes before Sapling, %d bytes once Sapling activates, and %d bytes once Cosmos activates.", MAX_TX_SIZE_BEFORE_SAPLING, MAX_TX_SIZE_AFTER_SAPLING, MAX_TX_SIZE_COSMOS) + HelpRequiringPassphrase() + "\n" "\nArguments:\n" "1. \"fromaddress\" (string, required) The address is a taddr or \"*\" for all taddrs belonging to the wallet.\n" @@ -4001,6 +4006,9 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp) if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING; } + if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_COSMOS)) { + max_tx_size = MAX_TX_SIZE_COSMOS; + } // If Sapling is not active, do not allow sending to a Sapling address. if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { @@ -4162,8 +4170,9 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp) "\nnumber of UTXOs. After Overwinter has activated -mempooltxinputlimit is ignored and having a transparent" "\ninput limit of zero will mean limit the number of UTXOs based on the size of the transaction. Any limit is" "\nconstrained by the consensus rule defining a maximum transaction size of " - + strprintf("%d bytes before Sapling, and %d", MAX_TX_SIZE_BEFORE_SAPLING, MAX_TX_SIZE_AFTER_SAPLING) - + "\nbytes once Sapling activates." + + strprintf("%d bytes before Sapling, %d", MAX_TX_SIZE_BEFORE_SAPLING, MAX_TX_SIZE_AFTER_SAPLING) + + "\nbytes once Sapling activates, " + + strprintf("and %d bytes once Cosmos activates.", MAX_TX_SIZE_COSMOS) + HelpRequiringPassphrase() + "\n" "\nArguments:\n" "1. fromaddresses (array, required) A JSON array with addresses.\n" @@ -4262,6 +4271,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp) const int nextBlockHeight = chainActive.Height() + 1; const bool overwinterActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER); const bool saplingActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING); + const bool cosmosActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_COSMOS); // Validate the destination address auto destaddress = params[1].get_str(); @@ -4344,6 +4354,8 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp) size_t mempoolLimit = (nUTXOLimit != 0) ? nUTXOLimit : (overwinterActive ? 0 : (size_t)GetArg("-mempooltxinputlimit", 0)); unsigned int max_tx_size = saplingActive ? MAX_TX_SIZE_AFTER_SAPLING : MAX_TX_SIZE_BEFORE_SAPLING; + if (cosmosActive) max_tx_size = MAX_TX_SIZE_COSMOS; + size_t estimatedTxSize = 200; // tx overhead + wiggle room if (isToSproutZaddr) { estimatedTxSize += JOINSPLIT_SIZE; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 36b040c433e..8e5ef4bb315 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -13,10 +13,13 @@ #include "init.h" #include "key_io.h" #include "main.h" +#include "zeronode/budget.h" #include "net.h" #include "rpc/protocol.h" #include "script/script.h" #include "script/sign.h" +#include "zeronode/spork.h" +#include "zeronode/swifttx.h" #include "timedata.h" #include "utilmoneystr.h" #include "zcash/Note.hpp" @@ -935,6 +938,73 @@ void CWallet::AddToSpends(const uint256& wtxid) } } +bool CWallet::GetZeronodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet, std::string strTxHash, std::string strOutputIndex) +{ + // wait for reindex and/or import to finish + if (fImporting || fReindex) return false; + + // Find possible candidates + std::vector vPossibleCoins; + + AvailableCoins(vPossibleCoins, true, NULL, false, true, ONLY_10000); + if (vPossibleCoins.empty()) { + LogPrintf("CWallet::GetZeronodeVinAndKeys -- Could not locate any valid zeronode vin\n"); + return false; + } + + LogPrintf("strTxHash = %s\n", strTxHash.c_str()); + if (strTxHash.empty()) // No output specified, select the first one + return GetVinAndKeysFromOutput(vPossibleCoins[0], txinRet, pubKeyRet, keyRet); + + // Find specific vin + uint256 txHash = uint256S(strTxHash); + + int nOutputIndex; + try { + nOutputIndex = std::stoi(strOutputIndex.c_str()); + } catch (const std::exception& e) { + LogPrintf("%s: %s on strOutputIndex\n", __func__, e.what()); + return false; + } + + BOOST_FOREACH (COutput& out, vPossibleCoins) + if (out.tx->GetHash() == txHash && out.i == nOutputIndex) // found it! + return GetVinAndKeysFromOutput(out, txinRet, pubKeyRet, keyRet); + + LogPrintf("CWallet::GetZeronodeVinAndKeys -- Could not locate specified zeronode vin\n"); + return false; +} + +bool CWallet::GetVinAndKeysFromOutput(COutput out, CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet) +{ + // wait for reindex and/or import to finish + if (fImporting || fReindex) return false; + + CScript pubScript; + + txinRet = CTxIn(out.tx->GetHash(), out.i); + pubScript = out.tx->vout[out.i].scriptPubKey; // the inputs PubKey + + CTxDestination address1; + ExtractDestination(pubScript, address1); + + CKeyID* keyID; + keyID = boost::get(&address1); + + if (!keyID) { + LogPrintf("CWallet::GetVinAndKeysFromOutput -- Address does not refer to a key\n"); + return false; + } + + if (!GetKey(*keyID, keyRet)) { + LogPrintf("CWallet::GetVinAndKeysFromOutput -- Private key for address is not known\n"); + return false; + } + + pubKeyRet = keyRet.GetPubKey(); + return true; +} + void CWallet::ClearNoteWitnessCache() { LOCK(cs_wallet); @@ -1926,6 +1996,42 @@ CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) cons return ((IsMine(txout) & filter) ? txout.nValue : 0); } +bool CWallet::IsDenominated(const CTxIn& txin) const +{ + { + LOCK(cs_wallet); + map::const_iterator mi = mapWallet.find(txin.prevout.hash); + if (mi != mapWallet.end()) { + const CWalletTx& prev = (*mi).second; + if (txin.prevout.n < prev.vout.size()) return IsDenominatedAmount(prev.vout[txin.prevout.n].nValue); + } + } + return false; +} + +bool CWallet::IsDenominated(const CTransaction& tx) const +{ + /* + Return false if ANY inputs are non-denom + */ + bool ret = true; + BOOST_FOREACH (const CTxIn& txin, tx.vin) { + if (!IsDenominated(txin)) { + ret = false; + } + } + return ret; +} + + +bool CWallet::IsDenominatedAmount(CAmount nInputAmount) const +{ + BOOST_FOREACH (CAmount d, obfuScationDenominations) + if (nInputAmount == d) + return true; + return false; +} + bool CWallet::IsChange(const CTxOut& txout) const { // TODO: fix handling of 'change' outputs. The assumption is that any @@ -1948,6 +2054,127 @@ bool CWallet::IsChange(const CTxOut& txout) const return false; } +// Return sum of unlocked coins +CAmount CWalletTx::GetUnlockedCredit() const +{ + if (pwallet == 0) + return 0; + + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return 0; + + CAmount nCredit = 0; + uint256 hashTx = GetHash(); + for (unsigned int i = 0; i < vout.size(); i++) { + const CTxOut& txout = vout[i]; + + if (pwallet->IsSpent(hashTx, i) || pwallet->IsLockedCoin(hashTx, i)) continue; + if (fZeroNode && vout[i].nValue == 10000 * COIN) continue; // do not count MN-like outputs + + nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); + if (!MoneyRange(nCredit)) + throw std::runtime_error("CWalletTx::GetUnlockedCredit() : value out of range"); + } + + return nCredit; +} + + // Return sum of locked coins +CAmount CWalletTx::GetLockedCredit() const +{ + if (pwallet == 0) + return 0; + + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return 0; + + CAmount nCredit = 0; + uint256 hashTx = GetHash(); + for (unsigned int i = 0; i < vout.size(); i++) { + const CTxOut& txout = vout[i]; + + // Skip spent coins + if (pwallet->IsSpent(hashTx, i)) continue; + + // Add locked coins + if (pwallet->IsLockedCoin(hashTx, i)) { + nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); + } + + //Add zeronode collaterals which are handled likc locked coins + else if (fZeroNode && vout[i].nValue == 10000 * COIN) { + nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); + } + + if (!MoneyRange(nCredit)) + throw std::runtime_error("CWalletTx::GetLockedCredit() : value out of range"); + } + + return nCredit; +} + +CAmount CWalletTx::GetDenominatedCredit(bool unconfirmed, bool fUseCache) const +{ + if (pwallet == 0) + return 0; + + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsCoinBase() && GetBlocksToMaturity() > 0) + return 0; + + int nDepth = GetDepthInMainChain(false); + if (nDepth < 0) return 0; + + // CheckFinalTx() uses chainActive.Height()+1 to evaluate + // nLockTime because when IsFinalTx() is called within + // CBlock::AcceptBlock(), the height of the block *being* + // evaluated is what is used. Thus if we want to know if a + // transaction can be part of the *next* block, we need to call + // IsFinalTx() with one more than chainActive.Height(). + const int nBlockHeight = chainActive.Height() + 1; + + // Timestamps on the other hand don't get any special treatment, + // because we can't know what timestamp the next block will have, + // and there aren't timestamp applications where it matters. + // However this changes once median past time-locks are enforced: + const int64_t nBlockTime = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST) + ? chainActive.Tip()->GetMedianTimePast() + : GetAdjustedTime(); + + bool isUnconfirmed = !IsFinalTx(*this, nBlockHeight, nBlockTime) || (!IsTrusted() && nDepth == 0); + if (unconfirmed != isUnconfirmed) return 0; + + if (fUseCache) { + if (unconfirmed && fDenomUnconfCreditCached) + return nDenomUnconfCreditCached; + else if (!unconfirmed && fDenomConfCreditCached) + return nDenomConfCreditCached; + } + + CAmount nCredit = 0; + uint256 hashTx = GetHash(); + for (unsigned int i = 0; i < vout.size(); i++) { + const CTxOut& txout = vout[i]; + + if (pwallet->IsSpent(hashTx, i) || !pwallet->IsDenominatedAmount(vout[i].nValue)) continue; + + nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); + if (!MoneyRange(nCredit)) + throw std::runtime_error("CWalletTx::GetDenominatedCredit() : value out of range"); + } + + if (unconfirmed) { + nDenomUnconfCreditCached = nCredit; + fDenomUnconfCreditCached = true; + } else { + nDenomConfCreditCached = nCredit; + fDenomConfCreditCached = true; + } + return nCredit; +} + CAmount CWallet::GetChange(const CTxOut& txout) const { if (!MoneyRange(txout.nValue)) @@ -2502,14 +2729,21 @@ void CWallet::ReacceptWalletTransactions() } } -bool CWalletTx::RelayWalletTransaction() +bool CWalletTx::RelayWalletTransaction(std::string strCommand) { assert(pwallet->GetBroadcastTransactions()); if (!IsCoinBase()) { if (GetDepthInMainChain() == 0) { - LogPrintf("Relaying wtx %s\n", GetHash().ToString()); - RelayTransaction((CTransaction)*this); + uint256 hash = GetHash(); + LogPrintf("Relaying wtx %s\n", hash.ToString()); + if(strCommand == "ix"){ + mapTxLockReq.insert(make_pair(hash, (CTransaction)*this)); + CreateNewLock(((CTransaction)*this)); + RelayTransactionLockReq((CTransaction)*this, true); + } else { + RelayTransaction((CTransaction)*this); + } return true; } } @@ -2663,15 +2897,20 @@ CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const return nAvailableWatchCreditCached; CAmount nCredit = 0; - for (unsigned int i = 0; i < vout.size(); i++) - { - if (!pwallet->IsSpent(GetHash(), i)) - { - const CTxOut &txout = vout[i]; - nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY); + uint256 hashTx = GetHash(); + for (unsigned int i = 0; i < vout.size(); i++) { + const CTxOut& txout = vout[i]; + const CTxIn vin = CTxIn(hashTx, i); + + if (pwallet->IsSpent(hashTx, i) || pwallet->IsLockedCoin(hashTx, i)) continue; + if (fZeroNode && vout[i].nValue == 10000 * COIN) continue; // do not count MN-like outputs + + // const int rounds = pwallet->GetInputObfuscationRounds(vin); + // if (rounds >= -2 && rounds < nZeroSendRounds) { + nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); if (!MoneyRange(nCredit)) - throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range"); - } + throw std::runtime_error("CWalletTx::GetAnonamizableCredit() : value out of range"); + // } } nAvailableWatchCreditCached = nCredit; @@ -2789,6 +3028,59 @@ CAmount CWallet::GetBalance() const return nTotal; } +CAmount CWallet::GetDenominatedBalance(bool unconfirmed) const +{ + if (fLiteMode) return 0; + + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { + const CWalletTx* pcoin = &(*it).second; + + nTotal += pcoin->GetDenominatedCredit(unconfirmed); + } + } + + return nTotal; +} + +CAmount CWallet::GetUnlockedCoins() const +{ + if (fLiteMode) return 0; + + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { + const CWalletTx* pcoin = &(*it).second; + + if (pcoin->IsTrusted() && pcoin->GetDepthInMainChain() > 0) + nTotal += pcoin->GetUnlockedCredit(); + } + } + + return nTotal; +} + +CAmount CWallet::GetLockedCoins() const +{ + if (fLiteMode) return 0; + + CAmount nTotal = 0; + { + LOCK2(cs_main, cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { + const CWalletTx* pcoin = &(*it).second; + + if (pcoin->IsTrusted() && pcoin->GetDepthInMainChain() > 0) + nTotal += pcoin->GetLockedCredit(); + } + } + + return nTotal; +} + CAmount CWallet::GetUnconfirmedBalance() const { CAmount nTotal = 0; @@ -2866,43 +3158,82 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const /** * populate vCoins with vector of available COutputs. */ -void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue, bool fIncludeCoinBase) const -{ - vCoins.clear(); - - { - LOCK2(cs_main, cs_wallet); - for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) - { - const uint256& wtxid = it->first; - const CWalletTx* pcoin = &(*it).second; - - if (!CheckFinalTx(*pcoin)) - continue; - - if (fOnlyConfirmed && !pcoin->IsTrusted()) - continue; + void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue, bool fIncludeCoinBase, AvailableCoinsType coin_type, bool useIX) const + { + vCoins.clear(); + + { + LOCK2(cs_main, cs_wallet); + int count = 1; + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const uint256& wtxid = it->first; + const CWalletTx* pcoin = &(*it).second; + + if (!CheckFinalTx(*pcoin)) + continue; - if (pcoin->IsCoinBase() && !fIncludeCoinBase) - continue; + if (fOnlyConfirmed && !pcoin->IsTrusted()) + continue; - if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) - continue; + if (pcoin->IsCoinBase() && !fIncludeCoinBase) + continue; - int nDepth = pcoin->GetDepthInMainChain(); - if (nDepth < 0) - continue; + if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) + continue; - for (unsigned int i = 0; i < pcoin->vout.size(); i++) { - isminetype mine = IsMine(pcoin->vout[i]); - if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && - !IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) && - (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected((*it).first, i))) - vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO)); - } - } - } -} + int nDepth = pcoin->GetDepthInMainChain(false); + if (useIX && nDepth < 6) + { + continue; + } + + for (unsigned int i = 0; i < pcoin->vout.size(); i++) { + bool found = false; + if (coin_type == ONLY_DENOMINATED) { + found = IsDenominatedAmount(pcoin->vout[i].nValue); + } else if (coin_type == ONLY_NOT10000IFMN) { + found = !(fZeroNode && pcoin->vout[i].nValue == 10000 * COIN); + } else if (coin_type == ONLY_NONDENOMINATED_NOT10000IFMN) { + //if (IsCollateralAmount(pcoin->vout[i].nValue)) continue; // do not use collateral amounts + found = !IsDenominatedAmount(pcoin->vout[i].nValue); + if (found && fZeroNode) found = pcoin->vout[i].nValue != 10000 * COIN; // do not use Hot MN funds + } else if (coin_type == ONLY_10000) { + found = pcoin->vout[i].nValue == 10000 * COIN; + } else { + found = true; + } + + if(!found) continue; + + isminetype mine = IsMine(pcoin->vout[i]); + + if (IsSpent(wtxid, i)) + { + continue; + } + if (mine == ISMINE_NO) + { + continue; + } + if (IsLockedCoin((*it).first, i) && coin_type != ONLY_10000) + { + continue; + } + if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected((*it).first, i)) + { + continue; + } + + bool fIsSpendable = false; + if ((mine & ISMINE_SPENDABLE) != ISMINE_NO) + fIsSpendable = true; + + vCoins.emplace_back(COutput(pcoin, i, nDepth, fIsSpendable)); + } + } + } + } static void ApproximateBestSubset(vector > >vValue, const CAmount& nTotalLower, const CAmount& nTargetValue, vector& vfBest, CAmount& nBest, int iterations = 1000) @@ -3051,12 +3382,12 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int return true; } -bool CWallet::SelectCoins(const CAmount& nTargetValue, set >& setCoinsRet, CAmount& nValueRet, bool& fOnlyCoinbaseCoinsRet, bool& fNeedCoinbaseCoinsRet, const CCoinControl* coinControl) const +bool CWallet::SelectCoins(const CAmount& nTargetValue, set >& setCoinsRet, CAmount& nValueRet, bool& fOnlyCoinbaseCoinsRet, bool& fNeedCoinbaseCoinsRet, const CCoinControl* coinControl, AvailableCoinsType coin_type, bool useIX) const { // Output parameter fOnlyCoinbaseCoinsRet is set to true when the only available coins are coinbase utxos. vector vCoinsNoCoinbase, vCoinsWithCoinbase; - AvailableCoins(vCoinsNoCoinbase, true, coinControl, false, false); - AvailableCoins(vCoinsWithCoinbase, true, coinControl, false, true); + AvailableCoins(vCoinsNoCoinbase, true, coinControl, false, false, coin_type, useIX); + AvailableCoins(vCoinsWithCoinbase, true, coinControl, false, true, coin_type, useIX); fOnlyCoinbaseCoinsRet = vCoinsNoCoinbase.size() == 0 && vCoinsWithCoinbase.size() > 0; // If coinbase utxos can only be sent to zaddrs, exclude any coinbase utxos from coin selection. @@ -3085,14 +3416,33 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set return all selected outputs (we want all selected to go into the transaction for sure) - if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) - { - BOOST_FOREACH(const COutput& out, vCoins) - { - if (!out.fSpendable) - continue; - nValueRet += out.tx->vout[out.i].nValue; - setCoinsRet.insert(make_pair(out.tx, out.i)); + if (coin_type != ONLY_DENOMINATED) { + if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs) + { + BOOST_FOREACH(const COutput& out, vCoins) + { + if (!out.fSpendable) + continue; + + nValueRet += out.tx->vout[out.i].nValue; + setCoinsRet.insert(make_pair(out.tx, out.i)); + } + return (nValueRet >= nTargetValue); + } + } + //if we're doing only denominated, we need to round up to the nearest .1 ZER + if (coin_type == ONLY_DENOMINATED) { + // Make outputs by looping through denominations, from large to small + BOOST_FOREACH (CAmount v, obfuScationDenominations) { + BOOST_FOREACH (const COutput& out, vCoins) { + if (out.tx->vout[out.i].nValue == v //make sure it's the denom we're looking for + && nValueRet + out.tx->vout[out.i].nValue < nTargetValue + (0.1 * COIN) + 100 //round the amount up to .1 ZER over + ) { + CTxIn vin = CTxIn(out.tx->GetHash(), out.i); + nValueRet += out.tx->vout[out.i].nValue; + setCoinsRet.insert(make_pair(out.tx, out.i)); + } + } } return (nValueRet >= nTargetValue); } @@ -3142,6 +3492,63 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set t2.Priority(); + } +}; + +bool CWallet::GetBudgetSystemCollateralTX(CTransaction& tx, uint256 hash, bool useIX) +{ + CWalletTx wtx; + if (GetBudgetSystemCollateralTX(wtx, hash, useIX)) { + tx = (CTransaction)wtx; + return true; + } + return false; +} + +bool CWallet::GetBudgetSystemCollateralTX(CWalletTx& tx, uint256 hash, bool useIX) +{ + // make our change address + CReserveKey reservekey(pwalletMain); + + CScript scriptChange; + scriptChange << OP_RETURN << ToByteVector(hash); + + CAmount nFeeRet = 0; + std::string strFail = ""; + vector vecSend; + CRecipient recipient = {scriptChange, BUDGET_FEE_TX, false}; + + CCoinControl* coinControl = NULL; + int nChangePosRet = -1; + bool success = CreateTransaction(vecSend, tx, reservekey, nFeeRet, nChangePosRet, strFail, coinControl, true, ALL_COINS, useIX, (CAmount)0); + if (!success) { + LogPrintf("GetBudgetSystemCollateralTX: Error - %s\n", strFail); + return false; + } + + return true; +} + +bool CWallet::ConvertList(std::vector vCoins, std::vector& vecAmounts) +{ + BOOST_FOREACH (CTxIn i, vCoins) { + if (mapWallet.count(i.prevout.hash)) { + CWalletTx& wtx = mapWallet[i.prevout.hash]; + if (i.prevout.n < wtx.vout.size()) { + vecAmounts.push_back(wtx.vout[i.prevout.n].nValue); + } + } else { + LogPrintf("ConvertList -- Couldn't find transaction\n"); + } + } + return true; +} + bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nChangePosRet, std::string& strFailReason) { vector vecSend; @@ -3187,8 +3594,10 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC } bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, - int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign) + int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, AvailableCoinsType coin_type, bool useIX, CAmount nFeePay) { + if(useIX && nFeePay < CENT) nFeePay = CENT; + CAmount nValue = 0; unsigned int nSubtractFeeFromAmount = 0; BOOST_FOREACH (const CRecipient& recipient, vecSend) @@ -3227,7 +3636,10 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING; } - + if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_COSMOS)) { + max_tx_size = MAX_TX_SIZE_COSMOS; + } + // Discourage fee sniping. // // However because of a off-by-one-error in previous versions we need to @@ -3254,6 +3666,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt LOCK2(cs_main, cs_wallet); { nFeeRet = 0; + if(nFeePay > 0) nFeeRet = nFeePay; while (true) { txNew.vin.clear(); @@ -3303,7 +3716,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt CAmount nValueIn = 0; bool fOnlyCoinbaseCoins = false; bool fNeedCoinbaseCoins = false; - if (!SelectCoins(nTotalValue, setCoins, nValueIn, fOnlyCoinbaseCoins, fNeedCoinbaseCoins, coinControl)) + if (!SelectCoins(nTotalValue, setCoins, nValueIn, fOnlyCoinbaseCoins, fNeedCoinbaseCoins, coinControl, coin_type, useIX)) { if (fOnlyCoinbaseCoins && Params().GetConsensus().fCoinbaseMustBeProtected) { strFailReason = _("Coinbase funds can only be sent to a zaddr"); @@ -3312,6 +3725,22 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt } else { strFailReason = _("Insufficient funds"); } + + if (coin_type == ALL_COINS) { + strFailReason += " " + _("Insufficient funds."); + } else if (coin_type == ONLY_NOT10000IFMN) { + strFailReason += " " + _("Unable to locate enough funds for this transaction that are not equal 10000 ZER."); + } else if (coin_type == ONLY_NONDENOMINATED_NOT10000IFMN) { + strFailReason += " " + _("Unable to locate enough Obfuscation non-denominated funds for this transaction that are not equal 10000 ZER."); + } else { + strFailReason += " " + _("Unable to locate enough Obfuscation denominated funds for this transaction."); + strFailReason += " " + _("Obfuscation uses exact denominated amounts to send funds, you might simply need to anonymize some more coins."); + } + + if (useIX) { + strFailReason += " " + _("SwiftX requires inputs with at least 6 confirmations, you might need to wait a few minutes and try again."); + } + return false; } BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) @@ -3511,10 +3940,21 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt return true; } +bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, AvailableCoinsType coin_type, bool useIX, CAmount nFeePay) +{ + vector vecSend; + + CRecipient recipient = {scriptPubKey, nValue, false}; + vecSend.push_back(recipient); + int nChangePos = -1; + return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, nChangePos, strFailReason, coinControl, true, coin_type, useIX, nFeePay); +} + /** * Call after CreateTransaction unless you want to abort */ -bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) +bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, std::string strCommand) { { LOCK2(cs_main, cs_wallet); @@ -3557,7 +3997,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) LogPrintf("CommitTransaction(): Error: Transaction not valid\n"); return false; } - wtxNew.RelayWalletTransaction(); + wtxNew.RelayWalletTransaction(strCommand); } } return true; @@ -4034,15 +4474,19 @@ void CWallet::GetAllReserveKeys(set& setAddress) const } } -void CWallet::UpdatedTransaction(const uint256 &hashTx) +bool CWallet::UpdatedTransaction(const uint256 &hashTx) { { LOCK(cs_wallet); // Only notify UI if this transaction is in this wallet map::const_iterator mi = mapWallet.find(hashTx); if (mi != mapWallet.end()) + { NotifyTransactionChanged(this, hashTx, CT_UPDATED); + return true; + } } + return false; } void CWallet::GetScriptForMining(boost::shared_ptr &script) @@ -4367,13 +4811,22 @@ int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const return chainActive.Height() - pindex->nHeight + 1; } -int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const +int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet, bool enableIX) const { AssertLockHeld(cs_main); int nResult = GetDepthInMainChainINTERNAL(pindexRet); if (nResult == 0 && !mempool.exists(GetHash())) return -1; // Not in chain, not in mempool + if(enableIX){ + if (nResult < 6){ + int signatures = GetTransactionLockSignatures(); + if(signatures >= SWIFTTX_SIGNATURES_REQUIRED){ + return nSwiftTXDepth+nResult; + } + } + } + return nResult; } @@ -4391,6 +4844,34 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee) return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, fRejectAbsurdFee); } +int CMerkleTx::GetTransactionLockSignatures() const +{ + if (fLargeWorkForkFound || fLargeWorkInvalidChainFound) return -2; + if (!IsSporkActive(SPORK_2_SWIFTTX)) return -3; + if (!fEnableSwiftTX) return -1; + + //compile consessus vote + std::map::iterator i = mapTxLocks.find(GetHash()); + if (i != mapTxLocks.end()) { + return (*i).second.CountSignatures(); + } + + return -1; +} + +bool CMerkleTx::IsTransactionLockTimedOut() const +{ + if (!fEnableSwiftTX) return 0; + + //compile consessus vote + std::map::iterator i = mapTxLocks.find(GetHash()); + if (i != mapTxLocks.end()) { + return GetTime() > (*i).second.nTimeout; + } + + return false; +} + /** * Find notes in the wallet filtered by payment address, min depth and ability to spend. * These notes are decrypted and added to the output parameter vector, outEntries. @@ -4413,7 +4894,7 @@ void CWallet::GetFilteredNotes( } /** - * Find notes in the wallet filtered by payment addresses, min depth, max depth, + * Find notes in the wallet filtered by payment addresses, min depth, max depth, * if the note is spent, if a spending key is required, and if the notes are locked. * These notes are decrypted and added to the output parameter vector, outEntries. */ diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d606399514e..b176dda43bf 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -87,6 +87,14 @@ enum WalletFeature FEATURE_LATEST = 60000 }; +enum AvailableCoinsType { + ALL_COINS = 1, + ONLY_DENOMINATED = 2, + ONLY_NOT10000IFMN = 3, + ONLY_NONDENOMINATED_NOT10000IFMN = 4, // ONLY_NONDENOMINATED and not 10000 ZER at the same time + ONLY_10000 = 5 // find zeronode outputs including locked ones (use with caution) +}; + /** A key pool entry */ class CKeyPool @@ -387,11 +395,13 @@ class CMerkleTx : public CTransaction * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ - int GetDepthInMainChain(const CBlockIndex* &pindexRet) const; - int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); } + int GetDepthInMainChain(const CBlockIndex* &pindexRet, bool enableIX = true) const; + int GetDepthInMainChain(bool enableIX = true) const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); } bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; } int GetBlocksToMaturity() const; bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true); + int GetTransactionLockSignatures() const; + bool IsTransactionLockTimedOut() const; }; /** @@ -420,6 +430,12 @@ class CWalletTx : public CMerkleTx mutable bool fCreditCached; mutable bool fImmatureCreditCached; mutable bool fAvailableCreditCached; + + // mutable bool fAnonymizableCreditCached; + // mutable bool fAnonymizedCreditCached; + mutable bool fDenomUnconfCreditCached; + mutable bool fDenomConfCreditCached; + mutable bool fWatchDebitCached; mutable bool fWatchCreditCached; mutable bool fImmatureWatchCreditCached; @@ -429,6 +445,12 @@ class CWalletTx : public CMerkleTx mutable CAmount nCreditCached; mutable CAmount nImmatureCreditCached; mutable CAmount nAvailableCreditCached; + + // mutable CAmount nAnonymizableCreditCached; + // mutable CAmount nAnonymizedCreditCached; + mutable CAmount nDenomUnconfCreditCached; + mutable CAmount nDenomConfCreditCached; + mutable CAmount nWatchDebitCached; mutable CAmount nWatchCreditCached; mutable CAmount nImmatureWatchCreditCached; @@ -471,6 +493,12 @@ class CWalletTx : public CMerkleTx fCreditCached = false; fImmatureCreditCached = false; fAvailableCreditCached = false; + + // fAnonymizableCreditCached = false; + // fAnonymizedCreditCached = false; + fDenomUnconfCreditCached = false; + fDenomConfCreditCached = false; + fWatchDebitCached = false; fWatchCreditCached = false; fImmatureWatchCreditCached = false; @@ -480,6 +508,12 @@ class CWalletTx : public CMerkleTx nCreditCached = 0; nImmatureCreditCached = 0; nAvailableCreditCached = 0; + + // nAnonymizableCreditCached = 0; + // nAnonymizedCreditCached = 0; + nDenomUnconfCreditCached = 0; + nDenomConfCreditCached = 0; + nWatchDebitCached = 0; nWatchCreditCached = 0; nAvailableWatchCreditCached = 0; @@ -564,6 +598,13 @@ class CWalletTx : public CMerkleTx CAmount GetCredit(const isminefilter& filter) const; CAmount GetImmatureCredit(bool fUseCache=true) const; CAmount GetAvailableCredit(bool fUseCache=true) const; + // CAmount GetAnonymizableCredit(bool fUseCache = true) const; + // CAmount GetAnonymizedCredit(bool fUseCache = true) const; + // Return sum of unlocked coins + CAmount GetUnlockedCredit() const; + // Return sum of unlocked coins + CAmount GetLockedCredit() const; + CAmount GetDenominatedCredit(bool unconfirmed, bool fUseCache = true) const; CAmount GetImmatureWatchOnlyCredit(const bool& fUseCache=true) const; CAmount GetAvailableWatchOnlyCredit(const bool& fUseCache=true) const; CAmount GetChange() const; @@ -586,7 +627,7 @@ class CWalletTx : public CMerkleTx int64_t GetTxTime() const; int GetRequestCount() const; - bool RelayWalletTransaction(); + bool RelayWalletTransaction(std::string strCommand="tx"); std::set GetConflicts() const; }; @@ -607,6 +648,22 @@ class COutput tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; } + //Used with Obfuscation. Will return largest nondenom, then denominations, then very small inputs + int Priority() const + { + BOOST_FOREACH (CAmount d, obfuScationDenominations) + if (tx->vout[i].nValue == d) return 10000; + if (tx->vout[i].nValue < 1 * COIN) return 20000; + + //nondenom return largest first + return -(tx->vout[i].nValue / COIN); + } + + CAmount Value() const + { + return tx->vout[i].nValue; + } + std::string ToString() const; }; @@ -730,7 +787,8 @@ class CAccountingEntry class CWallet : public CCryptoKeyStore, public CValidationInterface { private: - bool SelectCoins(const CAmount& nTargetValue, std::set >& setCoinsRet, CAmount& nValueRet, bool& fOnlyCoinbaseCoinsRet, bool& fNeedCoinbaseCoinsRet, const CCoinControl *coinControl = NULL) const; + bool SelectCoins(const CAmount& nTargetValue, std::set >& setCoinsRet, CAmount& nValueRet, bool& fOnlyCoinbaseCoinsRet, bool& fNeedCoinbaseCoinsRet, const CCoinControl *coinControl = NULL, + AvailableCoinsType coin_type = ALL_COINS, bool useIX = true) const; CWalletDB *pwalletdbEncryption; @@ -767,6 +825,13 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void AddToSpends(const uint256& wtxid); public: + //bool SelectCoinsDark(CAmount nValueMin, CAmount nValueMax, std::vector& setCoinsRet, CAmount& nValueRet, int nObfuscationRoundsMin, int nObfuscationRoundsMax) const; + //bool SelectCoinsByDenominations(int nDenom, CAmount nValueMin, CAmount nValueMax, std::vector& vCoinsRet, std::vector& vCoinsRet2, CAmount& nValueRet, int nObfuscationRoundsMin, int nObfuscationRoundsMax); + //bool SelectCoinsDarkDenominated(CAmount nTargetValue, std::vector& setCoinsRet, CAmount& nValueRet) const; + //bool HasCollateralInputs(bool fOnlyConfirmed = true) const; + //bool IsCollateralAmount(CAmount nInputAmount) const; + //int CountInputsWithAmount(CAmount nInputAmount); + //bool SelectCoinsCollateral(std::vector& setCoinsRet, CAmount& nValueRet) const; /* * Size of the incremental witness cache for the notes in our wallet. * This will always be greater than or equal to the size of the largest @@ -974,7 +1039,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! check whether we are allowed to upgrade (or already support) to the named feature bool CanSupportFeature(enum WalletFeature wf) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } - void AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false, bool fIncludeCoinBase=true) const; + void AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false, bool fIncludeCoinBase=true, AvailableCoinsType nCoinType = ALL_COINS, bool fUseIX = false) const; bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector vCoins, std::set >& setCoinsRet, CAmount& nValueRet) const; bool IsSpent(const uint256& hash, unsigned int n) const; @@ -993,6 +1058,11 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void UnlockAllSproutNotes(); std::vector ListLockedSproutNotes(); + /// Get 10000 Zer output and keys which can be used for the Zeronode + bool GetZeronodeVinAndKeys(CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet, std::string strTxHash = "", std::string strOutputIndex = ""); + /// Extract txin information and keys from output + bool GetVinAndKeysFromOutput(COutput out, CTxIn& txinRet, CPubKey& pubKeyRet, CKey& keyRet); + bool IsLockedNote(const SaplingOutPoint& output) const; void LockNote(const SaplingOutPoint& output); void UnlockNote(const SaplingOutPoint& output); @@ -1095,6 +1165,19 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool LoadCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk, const std::vector &vchCryptedSecret); + + bool ConvertList(std::vector vCoins, std::vector& vecAmounts); + //std::string PrepareObfuscationDenominate(int minRounds, int maxRounds); + int GenerateObfuscationOutputs(int nTotalValue, std::vector& vout); + //bool CreateCollateralTransaction(CMutableTransaction& txCollateral, std::string& strReason); + CAmount GetLockedCoins() const; + CAmount GetUnlockedCoins() const; + // CAmount GetAnonymizableBalance() const; + // CAmount GetAnonymizedBalance() const; + // double GetAverageAnonymizedRounds() const; + // CAmount GetNormalizedAnonymizedBalance() const; + CAmount GetDenominatedBalance(bool unconfirmed = false) const; + /** * Increment the next transaction order id * @return next transaction order id @@ -1136,8 +1219,10 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount GetImmatureWatchOnlyBalance() const; bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason); bool CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, - std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); - bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); + std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, AvailableCoinsType coin_type=ALL_COINS, bool useIX=false, CAmount nFeePay=0); + bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, + CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, AvailableCoinsType coin_type=ALL_COINS, bool useIX=false, CAmount nFeePay=0); + bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, std::string strCommand="tx"); static CFeeRate minTxFee; static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool); @@ -1156,6 +1241,19 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::set GetAccountAddresses(const std::string& strAccount) const; + bool GetBudgetSystemCollateralTX(CTransaction& tx, uint256 hash, bool useIX); + bool GetBudgetSystemCollateralTX(CWalletTx& tx, uint256 hash, bool useIX); + + // get the Obfuscation chain depth for a given input + //int GetRealInputObfuscationRounds(CTxIn in, int rounds) const; + // respect current settings + //int GetInputObfuscationRounds(CTxIn in) const; + + bool IsDenominated(const CTxIn& txin) const; + bool IsDenominated(const CTransaction& tx) const; + + bool IsDenominatedAmount(CAmount nInputAmount) const; + boost::optional GetSproutNoteNullifier( const JSDescription& jsdesc, const libzcash::SproutPaymentAddress& address, @@ -1202,7 +1300,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool DelAddressBook(const CTxDestination& address); - void UpdatedTransaction(const uint256 &hashTx); + bool UpdatedTransaction(const uint256 &hashTx); void Inventory(const uint256 &hash) { @@ -1220,7 +1318,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface LOCK(cs_wallet); mapRequestCount[hash] = 0; }; - + unsigned int GetKeyPoolSize() { AssertLockHeld(cs_wallet); // setKeyPool diff --git a/src/zeronode/activezeronode.cpp b/src/zeronode/activezeronode.cpp new file mode 100644 index 00000000000..4114b31c2c7 --- /dev/null +++ b/src/zeronode/activezeronode.cpp @@ -0,0 +1,425 @@ +// Copyright (c) 2014-2016 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zeronode/activezeronode.h" +#include "addrman.h" +#include "zeronode/zeronode.h" +#include "zeronode/zeronodeconfig.h" +#include "zeronode/zeronodeman.h" +#include "protocol.h" +#include "zeronode/spork.h" + +// +// Bootup the Zeronode, look for a 1000 Zero input and register on the network +// +void CActiveZeronode::ManageStatus() +{ + std::string errorMessage; + + if (!fZeroNode) return; + + if (fDebug) LogPrintf("CActiveZeronode::ManageStatus() - Begin\n"); + + //need correct blocks to send ping + if (NetworkIdFromCommandLine() != CBaseChainParams::REGTEST && !zeronodeSync.IsBlockchainSynced()) { + status = ACTIVE_ZERONODE_SYNC_IN_PROCESS; + LogPrintf("CActiveZeronode::ManageStatus() - %s\n", GetStatus()); + return; + } + + if (status == ACTIVE_ZERONODE_SYNC_IN_PROCESS) status = ACTIVE_ZERONODE_INITIAL; + + if (status == ACTIVE_ZERONODE_INITIAL) { + CZeronode* pzn; + pzn = znodeman.Find(pubKeyZeronode); + if (pzn != NULL) { + pzn->Check(); + if (pzn->IsEnabled() && pzn->protocolVersion == PROTOCOL_VERSION) EnableHotColdZeroNode(pzn->vin, pzn->addr); + } + } + + if (status != ACTIVE_ZERONODE_STARTED) { + // Set defaults + status = ACTIVE_ZERONODE_NOT_CAPABLE; + notCapableReason = ""; + + if (pwalletMain->IsLocked()) { + notCapableReason = "Wallet is locked."; + LogPrintf("CActiveZeronode::ManageStatus() - not capable: %s\n", notCapableReason); + return; + } + + if (pwalletMain->GetBalance() == 0) { + notCapableReason = "Hot node, waiting for remote activation."; + LogPrintf("CActiveZeronode::ManageStatus() - not capable: %s\n", notCapableReason); + return; + } + + if (strZeroNodeAddr.empty()) { + if (!GetLocal(service)) { + notCapableReason = "Can't detect external address. Please use the zeronodeaddr configuration option."; + LogPrintf("CActiveZeronode::ManageStatus() - not capable: %s\n", notCapableReason); + return; + } + } else { + service = CService(strZeroNodeAddr); + } + + LogPrintf("CActiveZeronode::ManageStatus() - Checking inbound connection to '%s'\n", service.ToString()); + + if(NetworkIdFromCommandLine() == CBaseChainParams::MAIN) { + if(service.GetPort() != 23801) { + notCapableReason = strprintf("Invalid port: %u - only 23801 is supported on mainnet.", service.GetPort()); + LogPrintf("CActiveZeronode::ManageStatus() - not capable: %s\n", notCapableReason); + return; + } + } else if(service.GetPort() == 23801) { + notCapableReason = strprintf("Invalid port: %u - 23801 is only supported on mainnet.", service.GetPort()); + LogPrintf("CActiveZeronode::ManageStatus() - not capable: %s\n", notCapableReason); + return; + } + + CNode* pnode = ConnectNode((CAddress)service, service.ToString().c_str()); + if (!pnode) { + notCapableReason = "Could not connect to " + service.ToString(); + LogPrintf("CActiveZeronode::ManageStatus() - not capable: %s\n", notCapableReason); + return; + } + pnode->Release(); + + // Choose coins to use + CPubKey pubKeyCollateralAddress; + CKey keyCollateralAddress; + + if (GetZeroNodeVin(vin, pubKeyCollateralAddress, keyCollateralAddress)) { + if (GetInputAge(vin) < ZERONODE_MIN_CONFIRMATIONS) { + status = ACTIVE_ZERONODE_INPUT_TOO_NEW; + notCapableReason = strprintf("%s - %d confirmations", GetStatus(), GetInputAge(vin)); + LogPrintf("CActiveZeronode::ManageStatus() - %s\n", notCapableReason); + return; + } + + LOCK(pwalletMain->cs_wallet); + pwalletMain->LockCoin(vin.prevout); + + // send to all nodes + CPubKey pubKeyZeronode; + CKey keyZeronode; + + if (!obfuScationSigner.SetKey(strZeroNodePrivKey, errorMessage, keyZeronode, pubKeyZeronode)) { + notCapableReason = "Error upon calling SetKey: " + errorMessage; + LogPrintf("Register::ManageStatus() - %s\n", notCapableReason); + return; + } + + if (!Register(vin, service, keyCollateralAddress, pubKeyCollateralAddress, keyZeronode, pubKeyZeronode, errorMessage)) { + notCapableReason = "Error on Register: " + errorMessage; + LogPrintf("Register::ManageStatus() - %s\n", notCapableReason); + return; + } + + LogPrintf("CActiveZeronode::ManageStatus() - Is capable master node!\n"); + status = ACTIVE_ZERONODE_STARTED; + + return; + } else { + notCapableReason = "Could not find suitable coins!"; + LogPrintf("CActiveZeronode::ManageStatus() - %s\n", notCapableReason); + return; + } + } + + //send to all peers + if (!SendZeronodePing(errorMessage)) { + LogPrintf("CActiveZeronode::ManageStatus() - Error on Ping: %s\n", errorMessage); + } +} + +std::string CActiveZeronode::GetStatus() +{ + switch (status) { + case ACTIVE_ZERONODE_INITIAL: + return "Node just started, not yet activated"; + case ACTIVE_ZERONODE_SYNC_IN_PROCESS: + return "Sync in progress. Must wait until sync is complete to start Zeronode"; + case ACTIVE_ZERONODE_INPUT_TOO_NEW: + return strprintf("Zeronode input must have at least %d confirmations", ZERONODE_MIN_CONFIRMATIONS); + case ACTIVE_ZERONODE_NOT_CAPABLE: + return "Not capable zeronode: " + notCapableReason; + case ACTIVE_ZERONODE_STARTED: + return "Zeronode successfully started"; + default: + return "unknown"; + } +} + +bool CActiveZeronode::SendZeronodePing(std::string& errorMessage) +{ + if (status != ACTIVE_ZERONODE_STARTED) { + errorMessage = "Zeronode is not in a running status"; + return false; + } + + CPubKey pubKeyZeronode; + CKey keyZeronode; + + if (!obfuScationSigner.SetKey(strZeroNodePrivKey, errorMessage, keyZeronode, pubKeyZeronode)) { + errorMessage = strprintf("Error upon calling SetKey: %s\n", errorMessage); + return false; + } + + LogPrintf("CActiveZeronode::SendZeronodePing() - Relay Zeronode Ping vin = %s\n", vin.ToString()); + + CZeronodePing znp(vin); + if (!znp.Sign(keyZeronode, pubKeyZeronode)) { + errorMessage = "Couldn't sign Zeronode Ping"; + return false; + } + + // Update lastPing for our zeronode in Zeronode list + CZeronode* pzn = znodeman.Find(vin); + if (pzn != NULL) { + if (pzn->IsPingedWithin(ZERONODE_PING_SECONDS, znp.sigTime)) { + errorMessage = "Too early to send Zeronode Ping"; + return false; + } + + pzn->lastPing = znp; + znodeman.mapSeenZeronodePing.insert(make_pair(znp.GetHash(), znp)); + + //znodeman.mapSeenZeronodeBroadcast.lastPing is probably outdated, so we'll update it + CZeronodeBroadcast znb(*pzn); + uint256 hash = znb.GetHash(); + if (znodeman.mapSeenZeronodeBroadcast.count(hash)) znodeman.mapSeenZeronodeBroadcast[hash].lastPing = znp; + + znp.Relay(); + return true; + } else { + // Seems like we are trying to send a ping while the Zeronode is not registered in the network + errorMessage = "Obfuscation Zeronode List doesn't include our Zeronode, shutting down Zeronode pinging service! " + vin.ToString(); + status = ACTIVE_ZERONODE_NOT_CAPABLE; + notCapableReason = errorMessage; + return false; + } +} + +bool CActiveZeronode::Register(std::string strService, std::string strKeyZeronode, std::string strTxHash, std::string strOutputIndex, std::string& errorMessage) +{ + CTxIn vin; + CPubKey pubKeyCollateralAddress; + CKey keyCollateralAddress; + CPubKey pubKeyZeronode; + CKey keyZeronode; + + //need correct blocks to send ping + if (!zeronodeSync.IsBlockchainSynced()) { + errorMessage = GetStatus(); + LogPrintf("CActiveZeronode::Register() - %s\n", errorMessage); + return false; + } + + if (!obfuScationSigner.SetKey(strKeyZeronode, errorMessage, keyZeronode, pubKeyZeronode)) { + errorMessage = strprintf("Can't find keys for zeronode %s - %s", strService, errorMessage); + LogPrintf("CActiveZeronode::Register() - %s\n", errorMessage); + return false; + } + + if (!GetZeroNodeVin(vin, pubKeyCollateralAddress, keyCollateralAddress, strTxHash, strOutputIndex)) { + errorMessage = strprintf("Could not allocate vin %s:%s for zeronode %s", strTxHash, strOutputIndex, strService); + LogPrintf("CActiveZeronode::Register() - %s\n", errorMessage); + return false; + } + + CService service; + if (!Lookup(strService.c_str(), service, 0, false)) + return LogPrintf("Invalid address %s for zeronode.", strService); + if(NetworkIdFromCommandLine() == CBaseChainParams::MAIN) { + if (service.GetPort() != 23801) { + errorMessage = strprintf("Invalid port %u for zeronode %s - only 23801 is supported on mainnet.", service.GetPort(), strService); + LogPrintf("CActiveZeronode::Register() - %s\n", errorMessage); + return false; + } + } else if (service.GetPort() == 23801) { + errorMessage = strprintf("Invalid port %u for zeronode %s - 23801 is only supported on mainnet.", service.GetPort(), strService); + LogPrintf("CActiveZeronode::Register() - %s\n", errorMessage); + return false; + } + + //addrman.Add(CAddress(service), CNetAddr("127.0.0.1"), 2 * 60 * 60); + + return Register(vin, CService(strService), keyCollateralAddress, pubKeyCollateralAddress, keyZeronode, pubKeyZeronode, errorMessage); +} + +bool CActiveZeronode::Register(CTxIn vin, CService service, CKey keyCollateralAddress, CPubKey pubKeyCollateralAddress, CKey keyZeronode, CPubKey pubKeyZeronode, std::string& errorMessage) +{ + CZeronodeBroadcast znb; + CZeronodePing znp(vin); + if (!znp.Sign(keyZeronode, pubKeyZeronode)) { + errorMessage = strprintf("Failed to sign ping, vin: %s", vin.ToString()); + LogPrintf("CActiveZeronode::Register() - %s\n", errorMessage); + return false; + } + znodeman.mapSeenZeronodePing.insert(make_pair(znp.GetHash(), znp)); + + LogPrintf("CActiveZeronode::Register() - Adding to Zeronode list\n service: %s\n vin: %s\n", service.ToString(), vin.ToString()); + znb = CZeronodeBroadcast(service, vin, pubKeyCollateralAddress, pubKeyZeronode, PROTOCOL_VERSION); + znb.lastPing = znp; + if (!znb.Sign(keyCollateralAddress)) { + errorMessage = strprintf("Failed to sign broadcast, vin: %s", vin.ToString()); + LogPrintf("CActiveZeronode::Register() - %s\n", errorMessage); + return false; + } + znodeman.mapSeenZeronodeBroadcast.insert(make_pair(znb.GetHash(), znb)); + zeronodeSync.AddedZeronodeList(znb.GetHash()); + + CZeronode* pzn = znodeman.Find(vin); + if (pzn == NULL) { + CZeronode zn(znb); + znodeman.Add(zn); + } else { + pzn->UpdateFromNewBroadcast(znb); + } + + //send to all peers + LogPrintf("CActiveZeronode::Register() - RelayElectionEntry vin = %s\n", vin.ToString()); + znb.Relay(); + return true; +} + +bool CActiveZeronode::GetZeroNodeVin(CTxIn& vin, CPubKey& pubkey, CKey& secretKey) +{ + return GetZeroNodeVin(vin, pubkey, secretKey, "", ""); +} + +bool CActiveZeronode::GetZeroNodeVin(CTxIn& vin, CPubKey& pubkey, CKey& secretKey, std::string strTxHash, std::string strOutputIndex) +{ + // Find possible candidates + TRY_LOCK(pwalletMain->cs_wallet, fWallet); + if (!fWallet) return false; + + vector possibleCoins = SelectCoinsZeronode(); + COutput* selectedOutput; + + // Find the vin + if (!strTxHash.empty()) { + // Let's find it + uint256 txHash = ArithToUint256(arith_uint256(strTxHash)); + int outputIndex; + try { + outputIndex = std::stoi(strOutputIndex.c_str()); + } catch (const std::exception& e) { + LogPrintf("%s: %s on strOutputIndex\n", __func__, e.what()); + return false; + } + + bool found = false; + BOOST_FOREACH (COutput& out, possibleCoins) { + if (out.tx->GetHash() == txHash && out.i == outputIndex) { + selectedOutput = &out; + found = true; + break; + } + } + if (!found) { + LogPrintf("CActiveZeronode::GetZeroNodeVin - Could not locate valid vin\n"); + return false; + } + } else { + // No output specified, Select the first one + if (possibleCoins.size() > 0) { + selectedOutput = &possibleCoins[0]; + } else { + LogPrintf("CActiveZeronode::GetZeroNodeVin - Could not locate specified vin from possible list\n"); + return false; + } + } + + // At this point we have a selected output, retrieve the associated info + return GetVinFromOutput(*selectedOutput, vin, pubkey, secretKey); +} + +// Extract Zeronode vin information from output +bool CActiveZeronode::GetVinFromOutput(COutput out, CTxIn& vin, CPubKey& pubkey, CKey& secretKey) +{ + CScript pubScript; + + vin = CTxIn(out.tx->GetHash(), out.i); + pubScript = out.tx->vout[out.i].scriptPubKey; // the inputs PubKey + + CTxDestination address; + ExtractDestination(pubScript, address); + + CKeyID *keyID = boost::get(&address); + if (!keyID) { + LogPrintf("CActiveZeronode::GetZeroNodeVin - Address does not refer to a key\n"); + return false; + } + + if (!pwalletMain->GetKey(*keyID, secretKey)) { + LogPrintf("CActiveZeronode::GetZeroNodeVin - Private key for address is not known\n"); + return false; + } + + pubkey = secretKey.GetPubKey(); + return true; +} + +// get all possible outputs for running Zeronode +vector CActiveZeronode::SelectCoinsZeronode() +{ + vector vCoins; + vector filteredCoins; + vector confLockedCoins; + + // Temporary unlock ZN coins from zeronode.conf + if (GetBoolArg("-znconflock", true)) { + uint256 znTxHash; + BOOST_FOREACH (CZeronodeConfig::CZeronodeEntry zne, zeronodeConfig.getEntries()) { + znTxHash.SetHex(zne.getTxHash()); + + int nIndex; + if(!zne.castOutputIndex(nIndex)) + continue; + + COutPoint outpoint = COutPoint(znTxHash, nIndex); + confLockedCoins.push_back(outpoint); + pwalletMain->UnlockCoin(outpoint); + } + } + + // Retrieve all possible outputs + pwalletMain->AvailableCoins(vCoins); + + // Lock ZN coins from zeronode.conf back if they where temporary unlocked + if (!confLockedCoins.empty()) { + BOOST_FOREACH (COutPoint outpoint, confLockedCoins) + pwalletMain->LockCoin(outpoint); + } + + // Filter + BOOST_FOREACH (const COutput& out, vCoins) { + if (out.tx->vout[out.i].nValue == 10000 * COIN) { //exactly + filteredCoins.push_back(out); + } + } + return filteredCoins; +} + +// when starting a Zeronode, this can enable to run as a hot wallet with no funds +bool CActiveZeronode::EnableHotColdZeroNode(CTxIn& newVin, CService& newService) +{ + if (!fZeroNode) return false; + + status = ACTIVE_ZERONODE_STARTED; + + //The values below are needed for signing znping messages going forward + vin = newVin; + service = newService; + + LogPrintf("CActiveZeronode::EnableHotColdZeroNode() - Enabled! You may shut down the cold daemon.\n"); + + return true; +} diff --git a/src/zeronode/activezeronode.h b/src/zeronode/activezeronode.h new file mode 100644 index 00000000000..504452fb559 --- /dev/null +++ b/src/zeronode/activezeronode.h @@ -0,0 +1,73 @@ +// Copyright (c) 2014-2016 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef ACTIVEZERONODE_H +#define ACTIVEZERONODE_H + +#include "init.h" +#include "key.h" +#include "zeronode/zeronode.h" +#include "net.h" +#include "zeronode/obfuscation.h" +#include "sync.h" +#include "wallet/wallet.h" + +#define ACTIVE_ZERONODE_INITIAL 0 // initial state +#define ACTIVE_ZERONODE_SYNC_IN_PROCESS 1 +#define ACTIVE_ZERONODE_INPUT_TOO_NEW 2 +#define ACTIVE_ZERONODE_NOT_CAPABLE 3 +#define ACTIVE_ZERONODE_STARTED 4 + +// Responsible for activating the Zeronode and pinging the network +class CActiveZeronode +{ +private: + // critical section to protect the inner data structures + mutable CCriticalSection cs; + + /// Ping Zeronode + bool SendZeronodePing(std::string& errorMessage); + + /// Register any Zeronode + bool Register(CTxIn vin, CService service, CKey key, CPubKey pubKey, CKey keyZeronode, CPubKey pubKeyZeronode, std::string& errorMessage); + + /// Get 10000 ZER input that can be used for the Zeronode + bool GetZeroNodeVin(CTxIn& vin, CPubKey& pubkey, CKey& secretKey, std::string strTxHash, std::string strOutputIndex); + bool GetVinFromOutput(COutput out, CTxIn& vin, CPubKey& pubkey, CKey& secretKey); + +public: + // Initialized by init.cpp + // Keys for the main Zeronode + CPubKey pubKeyZeronode; + + // Initialized while registering Zeronode + CTxIn vin; + CService service; + + int status; + std::string notCapableReason; + + CActiveZeronode() + { + status = ACTIVE_ZERONODE_INITIAL; + } + + /// Manage status of main Zeronode + void ManageStatus(); + std::string GetStatus(); + + /// Register remote Zeronode + bool Register(std::string strService, std::string strKey, std::string strTxHash, std::string strOutputIndex, std::string& errorMessage); + + /// Get 10000 ZER input that can be used for the Zeronode + bool GetZeroNodeVin(CTxIn& vin, CPubKey& pubkey, CKey& secretKey); + vector SelectCoinsZeronode(); + + /// Enable cold wallet mode (run a Zeronode with no funds) + bool EnableHotColdZeroNode(CTxIn& vin, CService& addr); +}; + +#endif diff --git a/src/zeronode/budget.cpp b/src/zeronode/budget.cpp new file mode 100644 index 00000000000..0ff44f4d41f --- /dev/null +++ b/src/zeronode/budget.cpp @@ -0,0 +1,2143 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "init.h" +#include "main.h" + +#include "addrman.h" +#include "zeronode/budget.h" +#include "zeronode/zeronode-sync.h" +#include "zeronode/zeronode.h" +#include "zeronode/zeronodeman.h" +#include "zeronode/obfuscation.h" +#include "util.h" +#include +#include + +#include "key_io.h" + +CBudgetManager budget; +CCriticalSection cs_budget; + +std::map askedForSourceProposalOrBudget; +std::vector vecImmatureBudgetProposals; +std::vector vecImmatureFinalizedBudgets; + +int nSubmittedFinalBudget; + +int GetBudgetPaymentCycleBlocks() +{ + // Amount of blocks in a months period of time (using 1 minutes per block) = (60*24*30) + if (NetworkIdFromCommandLine() == CBaseChainParams::MAIN) return 4070908800; //OFF in mainnet + //for testing purposes + + return 144; //ten times per day +} + +bool IsBudgetCollateralValid(uint256 nTxCollateralHash, uint256 nExpectedHash, std::string& strError, int64_t& nTime, int& nConf) +{ + CTransaction txCollateral; + uint256 nBlockHash; + if (!GetTransaction(nTxCollateralHash, txCollateral, nBlockHash, true)) { + strError = strprintf("Can't find collateral tx %s", txCollateral.ToString()); + LogPrint("zeronode","CBudgetProposalBroadcast::IsBudgetCollateralValid - %s\n", strError); + return false; + } + + if (txCollateral.vout.size() < 1) return false; + if (txCollateral.nLockTime != 0) return false; + + CScript findScript; + findScript << OP_RETURN << ToByteVector(nExpectedHash); + + bool foundOpReturn = false; + BOOST_FOREACH (const CTxOut o, txCollateral.vout) { + if (!o.scriptPubKey.IsNormalPaymentScript() && !o.scriptPubKey.IsUnspendable()) { + strError = strprintf("Invalid Script %s", txCollateral.ToString()); + LogPrint("zeronode","CBudgetProposalBroadcast::IsBudgetCollateralValid - %s\n", strError); + return false; + } + if (o.scriptPubKey == findScript && o.nValue >= PROPOSAL_FEE_TX) foundOpReturn = true; + } + if (!foundOpReturn) { + strError = strprintf("Couldn't find opReturn %s in %s", nExpectedHash.ToString(), txCollateral.ToString()); + LogPrint("zeronode","CBudgetProposalBroadcast::IsBudgetCollateralValid - %s\n", strError); + return false; + } + + // RETRIEVE CONFIRMATIONS AND NTIME + /* + - nTime starts as zero and is passed-by-reference out of this function and stored in the external proposal + - nTime is never validated via the hashing mechanism and comes from a full-validated source (the blockchain) + */ + + int conf = GetIXConfirmations(nTxCollateralHash); + if (nBlockHash != uint256()) { + BlockMap::iterator mi = mapBlockIndex.find(nBlockHash); + if (mi != mapBlockIndex.end() && (*mi).second) { + CBlockIndex* pindex = (*mi).second; + if (chainActive.Contains(pindex)) { + conf += chainActive.Height() - pindex->nHeight + 1; + nTime = pindex->nTime; + } + } + } + + nConf = conf; + + //if we're syncing we won't have swiftTX information, so accept 1 confirmation + if (conf >= Params().Budget_Fee_Confirmations()) { + return true; + } else { + strError = strprintf("Collateral requires at least %d confirmations - %d confirmations", Params().Budget_Fee_Confirmations(), conf); + LogPrint("zeronode","CBudgetProposalBroadcast::IsBudgetCollateralValid - %s - %d confirmations\n", strError, conf); + return false; + } +} + +void CBudgetManager::CheckOrphanVotes() +{ + LOCK(cs); + + + std::string strError = ""; + std::map::iterator it1 = mapOrphanZeronodeBudgetVotes.begin(); + while (it1 != mapOrphanZeronodeBudgetVotes.end()) { + if (budget.UpdateProposal(((*it1).second), NULL, strError)) { + LogPrint("zeronode","CBudgetManager::CheckOrphanVotes - Proposal/Budget is known, activating and removing orphan vote\n"); + mapOrphanZeronodeBudgetVotes.erase(it1++); + } else { + ++it1; + } + } + std::map::iterator it2 = mapOrphanFinalizedBudgetVotes.begin(); + while (it2 != mapOrphanFinalizedBudgetVotes.end()) { + if (budget.UpdateFinalizedBudget(((*it2).second), NULL, strError)) { + LogPrint("zeronode","CBudgetManager::CheckOrphanVotes - Proposal/Budget is known, activating and removing orphan vote\n"); + mapOrphanFinalizedBudgetVotes.erase(it2++); + } else { + ++it2; + } + } + LogPrint("zeronode","CBudgetManager::CheckOrphanVotes - Done\n"); +} + +void CBudgetManager::SubmitFinalBudget() +{ + static int nSubmittedHeight = 0; // height at which final budget was submitted last time + int nCurrentHeight; + + { + TRY_LOCK(cs_main, locked); + if (!locked) return; + if (!chainActive.Tip()) return; + nCurrentHeight = chainActive.Height(); + } + + int nBlockStart = nCurrentHeight - nCurrentHeight % GetBudgetPaymentCycleBlocks() + GetBudgetPaymentCycleBlocks(); + if (nSubmittedHeight >= nBlockStart){ + LogPrint("zeronode","CBudgetManager::SubmitFinalBudget - nSubmittedHeight(=%ld) < nBlockStart(=%ld) condition not fulfilled.\n", nSubmittedHeight, nBlockStart); + return; + } + // Submit final budget during the last 2 days before payment for Mainnet, about 9 minutes for Testnet + int nFinalizationStart = nBlockStart - ((GetBudgetPaymentCycleBlocks() / 30) * 2); + int nOffsetToStart = nFinalizationStart - nCurrentHeight; + + if (nBlockStart - nCurrentHeight > ((GetBudgetPaymentCycleBlocks() / 30) * 2)){ + LogPrint("zeronode","CBudgetManager::SubmitFinalBudget - Too early for finalization. Current block is %ld, next Superblock is %ld.\n", nCurrentHeight, nBlockStart); + LogPrint("zeronode","CBudgetManager::SubmitFinalBudget - First possible block for finalization: %ld. Last possible block for finalization: %ld. You have to wait for %ld block(s) until Budget finalization will be possible\n", nFinalizationStart, nBlockStart, nOffsetToStart); + + return; + } + + std::vector vBudgetProposals = budget.GetBudget(); + std::string strBudgetName = "main"; + std::vector vecTxBudgetPayments; + + for (unsigned int i = 0; i < vBudgetProposals.size(); i++) { + CTxBudgetPayment txBudgetPayment; + txBudgetPayment.nProposalHash = vBudgetProposals[i]->GetHash(); + txBudgetPayment.payee = vBudgetProposals[i]->GetPayee(); + txBudgetPayment.nAmount = vBudgetProposals[i]->GetAllotted(); + vecTxBudgetPayments.push_back(txBudgetPayment); + } + + if (vecTxBudgetPayments.size() < 1) { + LogPrint("zeronode","CBudgetManager::SubmitFinalBudget - Found No Proposals For Period\n"); + return; + } + + CFinalizedBudgetBroadcast tempBudget(strBudgetName, nBlockStart, vecTxBudgetPayments, uint256()); + if (mapSeenFinalizedBudgets.count(tempBudget.GetHash())) { + LogPrint("zeronode","CBudgetManager::SubmitFinalBudget - Budget already exists - %s\n", tempBudget.GetHash().ToString()); + nSubmittedHeight = nCurrentHeight; + return; //already exists + } + + //create fee tx + CTransaction tx; + uint256 txidCollateral; + + if (!mapCollateralTxids.count(tempBudget.GetHash())) { + CWalletTx wtx; + if (!pwalletMain->GetBudgetSystemCollateralTX(wtx, tempBudget.GetHash(), false)) { + LogPrint("zeronode","CBudgetManager::SubmitFinalBudget - Can't make collateral transaction\n"); + return; + } + + // Get our change address + CReserveKey reservekey(pwalletMain); + // Send the tx to the network. Do NOT use SwiftTx, locking might need too much time to propagate, especially for testnet + pwalletMain->CommitTransaction(wtx, reservekey, "NO-ix"); + tx = (CTransaction)wtx; + txidCollateral = tx.GetHash(); + mapCollateralTxids.insert(make_pair(tempBudget.GetHash(), txidCollateral)); + } else { + txidCollateral = mapCollateralTxids[tempBudget.GetHash()]; + } + + int conf = GetIXConfirmations(txidCollateral); + CTransaction txCollateral; + uint256 nBlockHash; + + if (!GetTransaction(txidCollateral, txCollateral, nBlockHash, true)) { + LogPrint("zeronode","CBudgetManager::SubmitFinalBudget - Can't find collateral tx %s", txidCollateral.ToString()); + return; + } + + if (nBlockHash != uint256()) { + BlockMap::iterator mi = mapBlockIndex.find(nBlockHash); + if (mi != mapBlockIndex.end() && (*mi).second) { + CBlockIndex* pindex = (*mi).second; + if (chainActive.Contains(pindex)) { + conf += chainActive.Height() - pindex->nHeight + 1; + } + } + } + + /* + Wait will we have 1 extra confirmation, otherwise some clients might reject this feeTX + -- This function is tied to NewBlock, so we will propagate this budget while the block is also propagating + */ + if (conf < Params().Budget_Fee_Confirmations() + 1) { + LogPrint("zeronode","CBudgetManager::SubmitFinalBudget - Collateral requires at least %d confirmations - %s - %d confirmations\n", Params().Budget_Fee_Confirmations() + 1, txidCollateral.ToString(), conf); + return; + } + + //create the proposal incase we're the first to make it + CFinalizedBudgetBroadcast finalizedBudgetBroadcast(strBudgetName, nBlockStart, vecTxBudgetPayments, txidCollateral); + + std::string strError = ""; + if (!finalizedBudgetBroadcast.IsValid(strError)) { + LogPrint("zeronode","CBudgetManager::SubmitFinalBudget - Invalid finalized budget - %s \n", strError); + return; + } + + LOCK(cs); + mapSeenFinalizedBudgets.insert(make_pair(finalizedBudgetBroadcast.GetHash(), finalizedBudgetBroadcast)); + finalizedBudgetBroadcast.Relay(); + budget.AddFinalizedBudget(finalizedBudgetBroadcast); + nSubmittedHeight = nCurrentHeight; + LogPrint("zeronode","CBudgetManager::SubmitFinalBudget - Done! %s\n", finalizedBudgetBroadcast.GetHash().ToString()); +} + +// +// CBudgetDB +// + +CBudgetDB::CBudgetDB() +{ + pathDB = GetDataDir() / "budget.dat"; + strMagicMessage = "ZeronodeBudget"; +} + +bool CBudgetDB::Write(const CBudgetManager& objToSave) +{ + LOCK(objToSave.cs); + + int64_t nStart = GetTimeMillis(); + + // serialize, checksum data up to that point, then append checksum + CDataStream ssObj(SER_DISK, CLIENT_VERSION); + ssObj << strMagicMessage; // zeronode cache file specific magic message + ssObj << FLATDATA(Params().MessageStart()); // network specific magic number + ssObj << objToSave; + uint256 hash = Hash(ssObj.begin(), ssObj.end()); + ssObj << hash; + + // open output file, and associate with CAutoFile + FILE* file = fopen(pathDB.string().c_str(), "wb"); + CAutoFile fileout(file, SER_DISK, CLIENT_VERSION); + if (fileout.IsNull()) + return error("%s : Failed to open file %s", __func__, pathDB.string()); + + // Write and commit header, data + try { + fileout << ssObj; + } catch (std::exception& e) { + return error("%s : Serialize or I/O error - %s", __func__, e.what()); + } + fileout.fclose(); + + LogPrint("zeronode","Written info to budget.dat %dms\n", GetTimeMillis() - nStart); + + return true; +} + +CBudgetDB::ReadResult CBudgetDB::Read(CBudgetManager& objToLoad, bool fDryRun) +{ + LOCK(objToLoad.cs); + + int64_t nStart = GetTimeMillis(); + // open input file, and associate with CAutoFile + FILE* file = fopen(pathDB.string().c_str(), "rb"); + CAutoFile filein(file, SER_DISK, CLIENT_VERSION); + if (filein.IsNull()) { + error("%s : Failed to open file %s", __func__, pathDB.string()); + return FileError; + } + + // use file size to size memory buffer + int fileSize = boost::filesystem::file_size(pathDB); + int dataSize = fileSize - sizeof(uint256); + // Don't try to resize to a negative number if file is small + if (dataSize < 0) + dataSize = 0; + vector vchData; + vchData.resize(dataSize); + uint256 hashIn; + + // read data and checksum from file + try { + filein.read((char*)&vchData[0], dataSize); + filein >> hashIn; + } catch (std::exception& e) { + error("%s : Deserialize or I/O error - %s", __func__, e.what()); + return HashReadError; + } + filein.fclose(); + + CDataStream ssObj(vchData, SER_DISK, CLIENT_VERSION); + + // verify stored checksum matches input data + uint256 hashTmp = Hash(ssObj.begin(), ssObj.end()); + if (hashIn != hashTmp) { + error("%s : Checksum mismatch, data corrupted", __func__); + return IncorrectHash; + } + + + unsigned char pchMsgTmp[4]; + std::string strMagicMessageTmp; + try { + // de-serialize file header (zeronode cache file specific magic message) and .. + ssObj >> strMagicMessageTmp; + + // ... verify the message matches predefined one + if (strMagicMessage != strMagicMessageTmp) { + error("%s : Invalid zeronode cache magic message", __func__); + return IncorrectMagicMessage; + } + + + // de-serialize file header (network specific magic number) and .. + ssObj >> FLATDATA(pchMsgTmp); + + // ... verify the network matches ours + if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp))) { + error("%s : Invalid network magic number", __func__); + return IncorrectMagicNumber; + } + + // de-serialize data into CBudgetManager object + ssObj >> objToLoad; + } catch (std::exception& e) { + objToLoad.Clear(); + error("%s : Deserialize or I/O error - %s", __func__, e.what()); + return IncorrectFormat; + } + + LogPrint("zeronode","Loaded info from budget.dat %dms\n", GetTimeMillis() - nStart); + LogPrint("zeronode"," %s\n", objToLoad.ToString()); + if (!fDryRun) { + LogPrint("zeronode","Budget manager - cleaning....\n"); + objToLoad.CheckAndRemove(); + LogPrint("zeronode","Budget manager - result:\n"); + LogPrint("zeronode"," %s\n", objToLoad.ToString()); + } + + return Ok; +} + +void DumpBudgets() +{ + int64_t nStart = GetTimeMillis(); + + CBudgetDB budgetdb; + CBudgetManager tempBudget; + + LogPrint("zeronode","Verifying budget.dat format...\n"); + CBudgetDB::ReadResult readResult = budgetdb.Read(tempBudget, true); + // there was an error and it was not an error on file opening => do not proceed + if (readResult == CBudgetDB::FileError) + LogPrint("zeronode","Missing budgets file - budget.dat, will try to recreate\n"); + else if (readResult != CBudgetDB::Ok) { + LogPrint("zeronode","Error reading budget.dat: "); + if (readResult == CBudgetDB::IncorrectFormat) + LogPrint("zeronode","magic is ok but data has invalid format, will try to recreate\n"); + else { + LogPrint("zeronode","file format is unknown or invalid, please fix it manually\n"); + return; + } + } + LogPrint("zeronode","Writting info to budget.dat...\n"); + budgetdb.Write(budget); + + LogPrint("zeronode","Budget dump finished %dms\n", GetTimeMillis() - nStart); +} + +bool CBudgetManager::AddFinalizedBudget(CFinalizedBudget& finalizedBudget) +{ + std::string strError = ""; + if (!finalizedBudget.IsValid(strError)) return false; + + if (mapFinalizedBudgets.count(finalizedBudget.GetHash())) { + return false; + } + + mapFinalizedBudgets.insert(make_pair(finalizedBudget.GetHash(), finalizedBudget)); + return true; +} + +bool CBudgetManager::AddProposal(CBudgetProposal& budgetProposal) +{ + LOCK(cs); + std::string strError = ""; + if (!budgetProposal.IsValid(strError)) { + LogPrint("zeronode","CBudgetManager::AddProposal - invalid budget proposal - %s\n", strError); + return false; + } + + if (mapProposals.count(budgetProposal.GetHash())) { + return false; + } + + mapProposals.insert(make_pair(budgetProposal.GetHash(), budgetProposal)); + LogPrint("zeronode","CBudgetManager::AddProposal - proposal %s added\n", budgetProposal.GetName ().c_str ()); + return true; +} + +void CBudgetManager::CheckAndRemove() +{ + LogPrint("znbudget", "CBudgetManager::CheckAndRemove\n"); + + // map tmpMapFinalizedBudgets; + // map tmpMapProposals; + + std::string strError = ""; + + LogPrint("znbudget", "CBudgetManager::CheckAndRemove - mapFinalizedBudgets cleanup - size before: %d\n", mapFinalizedBudgets.size()); + std::map::iterator it = mapFinalizedBudgets.begin(); + while (it != mapFinalizedBudgets.end()) { + CFinalizedBudget* pfinalizedBudget = &((*it).second); + + pfinalizedBudget->fValid = pfinalizedBudget->IsValid(strError); + if (!strError.empty ()) { + LogPrint("zeronode","CBudgetManager::CheckAndRemove - Invalid finalized budget: %s\n", strError); + } + else { + LogPrint("zeronode","CBudgetManager::CheckAndRemove - Found valid finalized budget: %s %s\n", + pfinalizedBudget->strBudgetName.c_str(), pfinalizedBudget->nFeeTXHash.ToString().c_str()); + } + + if (pfinalizedBudget->fValid) { + pfinalizedBudget->AutoCheck(); + // tmpMapFinalizedBudgets.insert(make_pair(pfinalizedBudget->GetHash(), *pfinalizedBudget)); + } + + ++it; + } + + LogPrint("znbudget", "CBudgetManager::CheckAndRemove - mapProposals cleanup - size before: %d\n", mapProposals.size()); + std::map::iterator it2 = mapProposals.begin(); + while (it2 != mapProposals.end()) { + CBudgetProposal* pbudgetProposal = &((*it2).second); + pbudgetProposal->fValid = pbudgetProposal->IsValid(strError); + if (!strError.empty ()) { + LogPrint("zeronode","CBudgetManager::CheckAndRemove - Invalid budget proposal - %s\n", strError); + strError = ""; + } + else { + LogPrint("zeronode","CBudgetManager::CheckAndRemove - Found valid budget proposal: %s %s\n", + pbudgetProposal->strProposalName.c_str(), pbudgetProposal->nFeeTXHash.ToString().c_str()); + } + if (pbudgetProposal->fValid) { + // tmpMapProposals.insert(make_pair(pbudgetProposal->GetHash(), *pbudgetProposal)); + } + + ++it2; + } + // Remove invalid entries by overwriting complete map + // mapFinalizedBudgets = tmpMapFinalizedBudgets; + // mapProposals = tmpMapProposals; + + // LogPrint("znbudget", "CBudgetManager::CheckAndRemove - mapFinalizedBudgets cleanup - size after: %d\n", mapFinalizedBudgets.size()); + // LogPrint("znbudget", "CBudgetManager::CheckAndRemove - mapProposals cleanup - size after: %d\n", mapProposals.size()); + LogPrint("zeronode","CBudgetManager::CheckAndRemove - PASSED\n"); + +} + +void CBudgetManager::FillBlockPayee(CMutableTransaction& txNew, CAmount nFees, CTxOut& txFounders, CTxOut& txZeronodes) +{ + LOCK(cs); + + CBlockIndex* pindexPrev = chainActive.Tip(); + if (!pindexPrev) return; + + int nHighestCount = 0; + CScript payee; + CAmount nAmount = 0; + + // ------- Grab The Highest Count + + std::map::iterator it = mapFinalizedBudgets.begin(); + while (it != mapFinalizedBudgets.end()) { + CFinalizedBudget* pfinalizedBudget = &((*it).second); + if (pfinalizedBudget->GetVoteCount() > nHighestCount && + pindexPrev->nHeight + 1 >= pfinalizedBudget->GetBlockStart() && + pindexPrev->nHeight + 1 <= pfinalizedBudget->GetBlockEnd() && + pfinalizedBudget->GetPayeeAndAmount(pindexPrev->nHeight + 1, payee, nAmount)) { + nHighestCount = pfinalizedBudget->GetVoteCount(); + } + + ++it; + } + + CAmount blockValue = GetBlockSubsidy(pindexPrev->nHeight + 1, Params().GetConsensus()); + + //miners get the full amount on these blocks + txNew.vout[0].nValue = blockValue; + + if ((pindexPrev->nHeight + 1 >= Params().GetConsensus().nFeeStartBlockHeight) && (pindexPrev->nHeight + 1 <= Params().GetConsensus().GetLastFoundersRewardBlockHeight())) { + // Founders reward is 7.5% of the block subsidy + CAmount vFoundersReward = txNew.vout[0].nValue * 7.5 / 100; + + // Take some reward away from us + txNew.vout[0].nValue -= vFoundersReward; + + // And give it to the founders + txFounders = CTxOut(vFoundersReward, Params().GetFoundersRewardScriptAtHeight(pindexPrev->nHeight + 1)); + txNew.vout.push_back(txFounders); + } + + if(nHighestCount > 0){ + txNew.vout[0].nValue -= nAmount; + + txZeronodes = CTxOut(nAmount, payee); + txNew.vout.push_back(txZeronodes); + + CTxDestination address1; + ExtractDestination(payee, address1); + + LogPrintf("Zeronode payment to %s\n", EncodeDestination(address1)); + } +} + +CFinalizedBudget* CBudgetManager::FindFinalizedBudget(uint256 nHash) +{ + if (mapFinalizedBudgets.count(nHash)) + return &mapFinalizedBudgets[nHash]; + + return NULL; +} + +CBudgetProposal* CBudgetManager::FindProposal(const std::string& strProposalName) +{ + //find the prop with the highest yes count + + int nYesCount = -99999; + CBudgetProposal* pbudgetProposal = NULL; + + std::map::iterator it = mapProposals.begin(); + while (it != mapProposals.end()) { + if ((*it).second.strProposalName == strProposalName && (*it).second.GetYeas() > nYesCount) { + pbudgetProposal = &((*it).second); + nYesCount = pbudgetProposal->GetYeas(); + } + ++it; + } + + if (nYesCount == -99999) return NULL; + + return pbudgetProposal; +} + +CBudgetProposal* CBudgetManager::FindProposal(uint256 nHash) +{ + LOCK(cs); + + if (mapProposals.count(nHash)) + return &mapProposals[nHash]; + + return NULL; +} + +bool CBudgetManager::IsBudgetPaymentBlock(int nBlockHeight) +{ + int nHighestCount = -1; + int nFivePercent = znodeman.CountEnabled(ActiveProtocol()) / 20; + + std::map::iterator it = mapFinalizedBudgets.begin(); + while (it != mapFinalizedBudgets.end()) { + CFinalizedBudget* pfinalizedBudget = &((*it).second); + if (pfinalizedBudget->GetVoteCount() > nHighestCount && + nBlockHeight >= pfinalizedBudget->GetBlockStart() && + nBlockHeight <= pfinalizedBudget->GetBlockEnd()) { + nHighestCount = pfinalizedBudget->GetVoteCount(); + } + + ++it; + } + + LogPrint("zeronode","CBudgetManager::IsBudgetPaymentBlock() - nHighestCount: %lli, 5%% of Zeronodes: %lli. Number of budgets: %lli\n", + nHighestCount, nFivePercent, mapFinalizedBudgets.size()); + + // If budget doesn't have 5% of the network votes, then we should pay a zeronode instead + if (nHighestCount > nFivePercent) return true; + + return false; +} + +bool CBudgetManager::IsTransactionValid(const CTransaction& txNew, int nBlockHeight) +{ + LOCK(cs); + + int nHighestCount = 0; + int nFivePercent = znodeman.CountEnabled(ActiveProtocol()) / 20; + std::vector ret; + + // ------- Grab The Highest Count + + std::map::iterator it = mapFinalizedBudgets.begin(); + while (it != mapFinalizedBudgets.end()) { + CFinalizedBudget* pfinalizedBudget = &((*it).second); + if (pfinalizedBudget->GetVoteCount() > nHighestCount && + nBlockHeight >= pfinalizedBudget->GetBlockStart() && + nBlockHeight <= pfinalizedBudget->GetBlockEnd()) { + nHighestCount = pfinalizedBudget->GetVoteCount(); + } + + ++it; + } + + LogPrint("zeronode","CBudgetManager::IsTransactionValid() - nHighestCount: %lli, 5%% of Zeronodes: %lli mapFinalizedBudgets.size(): %ld\n", + nHighestCount, nFivePercent, mapFinalizedBudgets.size()); + /* + If budget doesn't have 5% of the network votes, then we should pay a zeronode instead + */ + if (nHighestCount < nFivePercent) return false; + + // check the highest finalized budgets (+/- 10% to assist in consensus) + + it = mapFinalizedBudgets.begin(); + while (it != mapFinalizedBudgets.end()) { + CFinalizedBudget* pfinalizedBudget = &((*it).second); + + if (pfinalizedBudget->GetVoteCount() > nHighestCount - znodeman.CountEnabled(ActiveProtocol()) / 10) { + if (nBlockHeight >= pfinalizedBudget->GetBlockStart() && nBlockHeight <= pfinalizedBudget->GetBlockEnd()) { + if (pfinalizedBudget->IsTransactionValid(txNew, nBlockHeight)) { + return true; + } + } + } + + ++it; + } + + //we looked through all of the known budgets + return false; +} + +std::vector CBudgetManager::GetAllProposals() +{ + LOCK(cs); + + std::vector vBudgetProposalRet; + + std::map::iterator it = mapProposals.begin(); + while (it != mapProposals.end()) { + (*it).second.CleanAndRemove(false); + + CBudgetProposal* pbudgetProposal = &((*it).second); + vBudgetProposalRet.push_back(pbudgetProposal); + + ++it; + } + + return vBudgetProposalRet; +} + +// +// Sort by votes, if there's a tie sort by their feeHash TX +// +struct sortProposalsByVotes { + bool operator()(const std::pair& left, const std::pair& right) + { + if (left.second != right.second) + return (left.second > right.second); + return (UintToArith256(left.first->nFeeTXHash) > UintToArith256(right.first->nFeeTXHash)); + } +}; + +//Need to review this function +std::vector CBudgetManager::GetBudget() +{ + LOCK(cs); + + // ------- Sort budgets by Yes Count + + std::vector > vBudgetPorposalsSort; + + std::map::iterator it = mapProposals.begin(); + while (it != mapProposals.end()) { + (*it).second.CleanAndRemove(false); + vBudgetPorposalsSort.push_back(make_pair(&((*it).second), (*it).second.GetYeas() - (*it).second.GetNays())); + ++it; + } + + std::sort(vBudgetPorposalsSort.begin(), vBudgetPorposalsSort.end(), sortProposalsByVotes()); + + // ------- Grab The Budgets In Order + + std::vector vBudgetProposalsRet; + + CAmount nBudgetAllocated = 0; + CBlockIndex* pindexPrev = chainActive.Tip(); + if (pindexPrev == NULL) return vBudgetProposalsRet; + + int nBlockStart = pindexPrev->nHeight - pindexPrev->nHeight % GetBudgetPaymentCycleBlocks() + GetBudgetPaymentCycleBlocks(); + int nBlockEnd = nBlockStart + GetBudgetPaymentCycleBlocks() - 1; + CAmount nTotalBudget = GetTotalBudget(nBlockStart); + + + std::vector >::iterator it2 = vBudgetPorposalsSort.begin(); + while (it2 != vBudgetPorposalsSort.end()) { + CBudgetProposal* pbudgetProposal = (*it2).first; + + LogPrint("zeronode","CBudgetManager::GetBudget() - Processing Budget %s\n", pbudgetProposal->strProposalName.c_str()); + //prop start/end should be inside this period + if (pbudgetProposal->fValid && pbudgetProposal->nBlockStart <= nBlockStart && + pbudgetProposal->nBlockEnd >= nBlockEnd && + pbudgetProposal->GetYeas() - pbudgetProposal->GetNays() > znodeman.CountEnabled(ActiveProtocol()) / 10 && + pbudgetProposal->IsEstablished()) { + + LogPrint("zeronode","CBudgetManager::GetBudget() - Check 1 passed: valid=%d | %ld <= %ld | %ld >= %ld | Yeas=%d Nays=%d Count=%d | established=%d\n", + pbudgetProposal->fValid, pbudgetProposal->nBlockStart, nBlockStart, pbudgetProposal->nBlockEnd, + nBlockEnd, pbudgetProposal->GetYeas(), pbudgetProposal->GetNays(), znodeman.CountEnabled(ActiveProtocol()) / 10, + pbudgetProposal->IsEstablished()); + + if (pbudgetProposal->GetAmount() + nBudgetAllocated <= nTotalBudget) { + pbudgetProposal->SetAllotted(pbudgetProposal->GetAmount()); + nBudgetAllocated += pbudgetProposal->GetAmount(); + vBudgetProposalsRet.push_back(pbudgetProposal); + LogPrint("zeronode","CBudgetManager::GetBudget() - Check 2 passed: Budget added\n"); + } else { + pbudgetProposal->SetAllotted(0); + LogPrint("zeronode","CBudgetManager::GetBudget() - Check 2 failed: no amount allotted\n"); + } + } + else { + LogPrint("zeronode","CBudgetManager::GetBudget() - Check 1 failed: valid=%d | %ld <= %ld | %ld >= %ld | Yeas=%d Nays=%d Count=%d | established=%d\n", + pbudgetProposal->fValid, pbudgetProposal->nBlockStart, nBlockStart, pbudgetProposal->nBlockEnd, + nBlockEnd, pbudgetProposal->GetYeas(), pbudgetProposal->GetNays(), znodeman.CountEnabled(ActiveProtocol()) / 10, + pbudgetProposal->IsEstablished()); + } + + ++it2; + } + + return vBudgetProposalsRet; +} + +struct sortFinalizedBudgetsByVotes { + bool operator()(const std::pair& left, const std::pair& right) + { + return left.second > right.second; + } +}; + +std::vector CBudgetManager::GetFinalizedBudgets() +{ + LOCK(cs); + + std::vector vFinalizedBudgetsRet; + std::vector > vFinalizedBudgetsSort; + + // ------- Grab The Budgets In Order + + std::map::iterator it = mapFinalizedBudgets.begin(); + while (it != mapFinalizedBudgets.end()) { + CFinalizedBudget* pfinalizedBudget = &((*it).second); + + vFinalizedBudgetsSort.push_back(make_pair(pfinalizedBudget, pfinalizedBudget->GetVoteCount())); + ++it; + } + std::sort(vFinalizedBudgetsSort.begin(), vFinalizedBudgetsSort.end(), sortFinalizedBudgetsByVotes()); + + std::vector >::iterator it2 = vFinalizedBudgetsSort.begin(); + while (it2 != vFinalizedBudgetsSort.end()) { + vFinalizedBudgetsRet.push_back((*it2).first); + ++it2; + } + + return vFinalizedBudgetsRet; +} + +std::string CBudgetManager::GetRequiredPaymentsString(int nBlockHeight) +{ + LOCK(cs); + + std::string ret = "unknown-budget"; + + std::map::iterator it = mapFinalizedBudgets.begin(); + while (it != mapFinalizedBudgets.end()) { + CFinalizedBudget* pfinalizedBudget = &((*it).second); + if (nBlockHeight >= pfinalizedBudget->GetBlockStart() && nBlockHeight <= pfinalizedBudget->GetBlockEnd()) { + CTxBudgetPayment payment; + if (pfinalizedBudget->GetBudgetPaymentByBlock(nBlockHeight, payment)) { + if (ret == "unknown-budget") { + ret = payment.nProposalHash.ToString(); + } else { + ret += ","; + ret += payment.nProposalHash.ToString(); + } + } else { + LogPrint("zeronode","CBudgetManager::GetRequiredPaymentsString - Couldn't find budget payment for block %d\n", nBlockHeight); + } + } + + ++it; + } + + return ret; +} + +CAmount CBudgetManager::GetTotalBudget(int nHeight) +{ + if (chainActive.Tip() == NULL) return 0; + + if (NetworkIdFromCommandLine() == CBaseChainParams::TESTNET) { + CAmount nSubsidy = 500 * COIN; + return ((nSubsidy / 100) * 10) * 1440; + } + + CAmount nSubsidy = 500 * COIN; + return ((nSubsidy / 100) * 10) * 1440; +} + +void CBudgetManager::NewBlock() +{ + TRY_LOCK(cs, fBudgetNewBlock); + if (!fBudgetNewBlock) return; + + if (zeronodeSync.RequestedZeronodeAssets <= ZERONODE_SYNC_BUDGET) return; + + if (strBudgetMode == "suggest") { //suggest the budget we see + SubmitFinalBudget(); + } + + //this function should be called 1/14 blocks, allowing up to 100 votes per day on all proposals + if (chainActive.Height() % 14 != 0) return; + + // incremental sync with our peers + if (zeronodeSync.IsSynced()) { + LogPrint("zeronode","CBudgetManager::NewBlock - incremental sync started\n"); + if (chainActive.Height() % 1440 == rand() % 1440) { + ClearSeen(); + ResetSync(); + } + + LOCK(cs_vNodes); + BOOST_FOREACH (CNode* pnode, vNodes) + if (pnode->nVersion >= ActiveProtocol()) + Sync(pnode, uint256(), true); + + MarkSynced(); + } + + + CheckAndRemove(); + + //remove invalid votes once in a while (we have to check the signatures and validity of every vote, somewhat CPU intensive) + + LogPrint("zeronode","CBudgetManager::NewBlock - askedForSourceProposalOrBudget cleanup - size: %d\n", askedForSourceProposalOrBudget.size()); + std::map::iterator it = askedForSourceProposalOrBudget.begin(); + while (it != askedForSourceProposalOrBudget.end()) { + if ((*it).second > GetTime() - (60 * 60 * 24)) { + ++it; + } else { + askedForSourceProposalOrBudget.erase(it++); + } + } + + LogPrint("zeronode","CBudgetManager::NewBlock - mapProposals cleanup - size: %d\n", mapProposals.size()); + std::map::iterator it2 = mapProposals.begin(); + while (it2 != mapProposals.end()) { + (*it2).second.CleanAndRemove(false); + ++it2; + } + + LogPrint("zeronode","CBudgetManager::NewBlock - mapFinalizedBudgets cleanup - size: %d\n", mapFinalizedBudgets.size()); + std::map::iterator it3 = mapFinalizedBudgets.begin(); + while (it3 != mapFinalizedBudgets.end()) { + (*it3).second.CleanAndRemove(false); + ++it3; + } + + LogPrint("zeronode","CBudgetManager::NewBlock - vecImmatureBudgetProposals cleanup - size: %d\n", vecImmatureBudgetProposals.size()); + std::vector::iterator it4 = vecImmatureBudgetProposals.begin(); + while (it4 != vecImmatureBudgetProposals.end()) { + std::string strError = ""; + int nConf = 0; + if (!IsBudgetCollateralValid((*it4).nFeeTXHash, (*it4).GetHash(), strError, (*it4).nTime, nConf)) { + ++it4; + continue; + } + + if (!(*it4).IsValid(strError)) { + LogPrint("zeronode","zprop (immature) - invalid budget proposal - %s\n", strError); + it4 = vecImmatureBudgetProposals.erase(it4); + continue; + } + + CBudgetProposal budgetProposal((*it4)); + if (AddProposal(budgetProposal)) { + (*it4).Relay(); + } + + LogPrint("zeronode","zprop (immature) - new budget - %s\n", (*it4).GetHash().ToString()); + it4 = vecImmatureBudgetProposals.erase(it4); + } + + LogPrint("zeronode","CBudgetManager::NewBlock - vecImmatureFinalizedBudgets cleanup - size: %d\n", vecImmatureFinalizedBudgets.size()); + std::vector::iterator it5 = vecImmatureFinalizedBudgets.begin(); + while (it5 != vecImmatureFinalizedBudgets.end()) { + std::string strError = ""; + int nConf = 0; + if (!IsBudgetCollateralValid((*it5).nFeeTXHash, (*it5).GetHash(), strError, (*it5).nTime, nConf)) { + ++it5; + continue; + } + + if (!(*it5).IsValid(strError)) { + LogPrint("zeronode","fbs (immature) - invalid finalized budget - %s\n", strError); + it5 = vecImmatureFinalizedBudgets.erase(it5); + continue; + } + + LogPrint("zeronode","fbs (immature) - new finalized budget - %s\n", (*it5).GetHash().ToString()); + + CFinalizedBudget finalizedBudget((*it5)); + if (AddFinalizedBudget(finalizedBudget)) { + (*it5).Relay(); + } + + it5 = vecImmatureFinalizedBudgets.erase(it5); + } + LogPrint("zeronode","CBudgetManager::NewBlock - PASSED\n"); +} + +void CBudgetManager::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) +{ + // lite mode is not supported + if (fLiteMode) return; + if (!zeronodeSync.IsBlockchainSynced()) return; + + LOCK(cs_budget); + + if (strCommand == "znvs") { //Zeronode vote sync + uint256 nProp; + vRecv >> nProp; + + if (NetworkIdFromCommandLine() == CBaseChainParams::MAIN) { + if (nProp == uint256()) { + if (pfrom->HasFulfilledRequest("znvs")) { + LogPrint("zeronode","znvs - peer already asked me for the list\n"); + Misbehaving(pfrom->GetId(), 20); + return; + } + pfrom->FulfilledRequest("znvs"); + } + } + + Sync(pfrom, nProp); + LogPrint("znbudget", "znvs - Sent Zeronode votes to peer %i\n", pfrom->GetId()); + } + + if (strCommand == "zprop") { //Zeronode Proposal + CBudgetProposalBroadcast budgetProposalBroadcast; + vRecv >> budgetProposalBroadcast; + + if (mapSeenZeronodeBudgetProposals.count(budgetProposalBroadcast.GetHash())) { + zeronodeSync.AddedBudgetItem(budgetProposalBroadcast.GetHash()); + return; + } + + std::string strError = ""; + int nConf = 0; + if (!IsBudgetCollateralValid(budgetProposalBroadcast.nFeeTXHash, budgetProposalBroadcast.GetHash(), strError, budgetProposalBroadcast.nTime, nConf)) { + LogPrint("zeronode","Proposal FeeTX is not valid - %s - %s\n", budgetProposalBroadcast.nFeeTXHash.ToString(), strError); + if (nConf >= 1) vecImmatureBudgetProposals.push_back(budgetProposalBroadcast); + return; + } + + mapSeenZeronodeBudgetProposals.insert(make_pair(budgetProposalBroadcast.GetHash(), budgetProposalBroadcast)); + + if (!budgetProposalBroadcast.IsValid(strError)) { + LogPrint("zeronode","zprop - invalid budget proposal - %s\n", strError); + return; + } + + CBudgetProposal budgetProposal(budgetProposalBroadcast); + if (AddProposal(budgetProposal)) { + budgetProposalBroadcast.Relay(); + } + zeronodeSync.AddedBudgetItem(budgetProposalBroadcast.GetHash()); + + LogPrint("zeronode","zprop - new budget - %s\n", budgetProposalBroadcast.GetHash().ToString()); + + //We might have active votes for this proposal that are valid now + CheckOrphanVotes(); + } + + if (strCommand == "mvote") { //Zeronode Vote + CBudgetVote vote; + vRecv >> vote; + vote.fValid = true; + + if (mapSeenZeronodeBudgetVotes.count(vote.GetHash())) { + zeronodeSync.AddedBudgetItem(vote.GetHash()); + return; + } + + CZeronode* pzn = znodeman.Find(vote.vin); + if (pzn == NULL) { + LogPrint("zeronode","mvote - unknown zeronode - vin: %s\n", vote.vin.prevout.hash.ToString()); + znodeman.AskForZN(pfrom, vote.vin); + return; + } + + + mapSeenZeronodeBudgetVotes.insert(make_pair(vote.GetHash(), vote)); + if (!vote.SignatureValid(true)) { + LogPrint("zeronode","mvote - signature invalid\n"); + if (zeronodeSync.IsSynced()) + { + Misbehaving(pfrom->GetId(), 20); + } + // it could just be a non-synced zeronode + znodeman.AskForZN(pfrom, vote.vin); + return; + } + + std::string strError = ""; + if (UpdateProposal(vote, pfrom, strError)) { + vote.Relay(); + zeronodeSync.AddedBudgetItem(vote.GetHash()); + } + + LogPrint("zeronode","mvote - new budget vote for budget %s - %s\n", vote.nProposalHash.ToString(), vote.GetHash().ToString()); + } + + if (strCommand == "fbs") { //Finalized Budget Suggestion + CFinalizedBudgetBroadcast finalizedBudgetBroadcast; + vRecv >> finalizedBudgetBroadcast; + + if (mapSeenFinalizedBudgets.count(finalizedBudgetBroadcast.GetHash())) { + zeronodeSync.AddedBudgetItem(finalizedBudgetBroadcast.GetHash()); + return; + } + + std::string strError = ""; + int nConf = 0; + if (!IsBudgetCollateralValid(finalizedBudgetBroadcast.nFeeTXHash, finalizedBudgetBroadcast.GetHash(), strError, finalizedBudgetBroadcast.nTime, nConf)) { + LogPrint("zeronode","Finalized Budget FeeTX is not valid - %s - %s\n", finalizedBudgetBroadcast.nFeeTXHash.ToString(), strError); + + if (nConf >= 1) vecImmatureFinalizedBudgets.push_back(finalizedBudgetBroadcast); + return; + } + + mapSeenFinalizedBudgets.insert(make_pair(finalizedBudgetBroadcast.GetHash(), finalizedBudgetBroadcast)); + + if (!finalizedBudgetBroadcast.IsValid(strError)) { + LogPrint("zeronode","fbs - invalid finalized budget - %s\n", strError); + return; + } + + LogPrint("zeronode","fbs - new finalized budget - %s\n", finalizedBudgetBroadcast.GetHash().ToString()); + + CFinalizedBudget finalizedBudget(finalizedBudgetBroadcast); + if (AddFinalizedBudget(finalizedBudget)) { + finalizedBudgetBroadcast.Relay(); + } + zeronodeSync.AddedBudgetItem(finalizedBudgetBroadcast.GetHash()); + + //we might have active votes for this budget that are now valid + CheckOrphanVotes(); + } + + if (strCommand == "fbvote") { //Finalized Budget Vote + CFinalizedBudgetVote vote; + vRecv >> vote; + vote.fValid = true; + + if (mapSeenFinalizedBudgetVotes.count(vote.GetHash())) { + zeronodeSync.AddedBudgetItem(vote.GetHash()); + return; + } + + CZeronode* pzn = znodeman.Find(vote.vin); + if (pzn == NULL) { + LogPrint("znbudget", "fbvote - unknown zeronode - vin: %s\n", vote.vin.prevout.hash.ToString()); + znodeman.AskForZN(pfrom, vote.vin); + return; + } + + mapSeenFinalizedBudgetVotes.insert(make_pair(vote.GetHash(), vote)); + if (!vote.SignatureValid(true)) { + LogPrint("zeronode","fbvote - signature invalid\n"); + if (zeronodeSync.IsSynced()) + { + Misbehaving(pfrom->GetId(), 20); + } + // it could just be a non-synced zeronode + znodeman.AskForZN(pfrom, vote.vin); + return; + } + + std::string strError = ""; + if (UpdateFinalizedBudget(vote, pfrom, strError)) { + vote.Relay(); + zeronodeSync.AddedBudgetItem(vote.GetHash()); + + LogPrint("zeronode","fbvote - new finalized budget vote - %s\n", vote.GetHash().ToString()); + } else { + LogPrint("zeronode","fbvote - rejected finalized budget vote - %s - %s\n", vote.GetHash().ToString(), strError); + } + } +} + +bool CBudgetManager::PropExists(uint256 nHash) +{ + if (mapProposals.count(nHash)) return true; + return false; +} + +//mark that a full sync is needed +void CBudgetManager::ResetSync() +{ + LOCK(cs); + + + std::map::iterator it1 = mapSeenZeronodeBudgetProposals.begin(); + while (it1 != mapSeenZeronodeBudgetProposals.end()) { + CBudgetProposal* pbudgetProposal = FindProposal((*it1).first); + if (pbudgetProposal && pbudgetProposal->fValid) { + //mark votes + std::map::iterator it2 = pbudgetProposal->mapVotes.begin(); + while (it2 != pbudgetProposal->mapVotes.end()) { + (*it2).second.fSynced = false; + ++it2; + } + } + ++it1; + } + + std::map::iterator it3 = mapSeenFinalizedBudgets.begin(); + while (it3 != mapSeenFinalizedBudgets.end()) { + CFinalizedBudget* pfinalizedBudget = FindFinalizedBudget((*it3).first); + if (pfinalizedBudget && pfinalizedBudget->fValid) { + //send votes + std::map::iterator it4 = pfinalizedBudget->mapVotes.begin(); + while (it4 != pfinalizedBudget->mapVotes.end()) { + (*it4).second.fSynced = false; + ++it4; + } + } + ++it3; + } +} + +void CBudgetManager::MarkSynced() +{ + LOCK(cs); + + /* + Mark that we've sent all valid items + */ + + std::map::iterator it1 = mapSeenZeronodeBudgetProposals.begin(); + while (it1 != mapSeenZeronodeBudgetProposals.end()) { + CBudgetProposal* pbudgetProposal = FindProposal((*it1).first); + if (pbudgetProposal && pbudgetProposal->fValid) { + //mark votes + std::map::iterator it2 = pbudgetProposal->mapVotes.begin(); + while (it2 != pbudgetProposal->mapVotes.end()) { + if ((*it2).second.fValid) + (*it2).second.fSynced = true; + ++it2; + } + } + ++it1; + } + + std::map::iterator it3 = mapSeenFinalizedBudgets.begin(); + while (it3 != mapSeenFinalizedBudgets.end()) { + CFinalizedBudget* pfinalizedBudget = FindFinalizedBudget((*it3).first); + if (pfinalizedBudget && pfinalizedBudget->fValid) { + //mark votes + std::map::iterator it4 = pfinalizedBudget->mapVotes.begin(); + while (it4 != pfinalizedBudget->mapVotes.end()) { + if ((*it4).second.fValid) + (*it4).second.fSynced = true; + ++it4; + } + } + ++it3; + } +} + + +void CBudgetManager::Sync(CNode* pfrom, uint256 nProp, bool fPartial) +{ + LOCK(cs); + + /* + Sync with a client on the network + + -- + + This code checks each of the hash maps for all known budget proposals and finalized budget proposals, then checks them against the + budget object to see if they're OK. If all checks pass, we'll send it to the peer. + + */ + + int nInvCount = 0; + + std::map::iterator it1 = mapSeenZeronodeBudgetProposals.begin(); + while (it1 != mapSeenZeronodeBudgetProposals.end()) { + CBudgetProposal* pbudgetProposal = FindProposal((*it1).first); + if (pbudgetProposal && pbudgetProposal->fValid && (nProp == uint256() || (*it1).first == nProp)) { + pfrom->PushInventory(CInv(MSG_BUDGET_PROPOSAL, (*it1).second.GetHash())); + nInvCount++; + + //send votes + std::map::iterator it2 = pbudgetProposal->mapVotes.begin(); + while (it2 != pbudgetProposal->mapVotes.end()) { + if ((*it2).second.fValid) { + if ((fPartial && !(*it2).second.fSynced) || !fPartial) { + pfrom->PushInventory(CInv(MSG_BUDGET_VOTE, (*it2).second.GetHash())); + nInvCount++; + } + } + ++it2; + } + } + ++it1; + } + + pfrom->PushMessage("ssc", ZERONODE_SYNC_BUDGET_PROP, nInvCount); + + LogPrint("znbudget", "CBudgetManager::Sync - sent %d items\n", nInvCount); + + nInvCount = 0; + + std::map::iterator it3 = mapSeenFinalizedBudgets.begin(); + while (it3 != mapSeenFinalizedBudgets.end()) { + CFinalizedBudget* pfinalizedBudget = FindFinalizedBudget((*it3).first); + if (pfinalizedBudget && pfinalizedBudget->fValid && (nProp == uint256() || (*it3).first == nProp)) { + pfrom->PushInventory(CInv(MSG_BUDGET_FINALIZED, (*it3).second.GetHash())); + nInvCount++; + + //send votes + std::map::iterator it4 = pfinalizedBudget->mapVotes.begin(); + while (it4 != pfinalizedBudget->mapVotes.end()) { + if ((*it4).second.fValid) { + if ((fPartial && !(*it4).second.fSynced) || !fPartial) { + pfrom->PushInventory(CInv(MSG_BUDGET_FINALIZED_VOTE, (*it4).second.GetHash())); + nInvCount++; + } + } + ++it4; + } + } + ++it3; + } + + pfrom->PushMessage("ssc", ZERONODE_SYNC_BUDGET_FIN, nInvCount); + LogPrint("znbudget", "CBudgetManager::Sync - sent %d items\n", nInvCount); +} + +bool CBudgetManager::UpdateProposal(CBudgetVote& vote, CNode* pfrom, std::string& strError) +{ + LOCK(cs); + + if (!mapProposals.count(vote.nProposalHash)) { + if (pfrom) { + // only ask for missing items after our syncing process is complete -- + // otherwise we'll think a full sync succeeded when they return a result + if (!zeronodeSync.IsSynced()) return false; + + LogPrint("zeronode","CBudgetManager::UpdateProposal - Unknown proposal %d, asking for source proposal\n", vote.nProposalHash.ToString()); + mapOrphanZeronodeBudgetVotes[vote.nProposalHash] = vote; + + if (!askedForSourceProposalOrBudget.count(vote.nProposalHash)) { + pfrom->PushMessage("znvs", vote.nProposalHash); + askedForSourceProposalOrBudget[vote.nProposalHash] = GetTime(); + } + } + + strError = "Proposal not found!"; + return false; + } + + + return mapProposals[vote.nProposalHash].AddOrUpdateVote(vote, strError); +} + +bool CBudgetManager::UpdateFinalizedBudget(CFinalizedBudgetVote& vote, CNode* pfrom, std::string& strError) +{ + LOCK(cs); + + if (!mapFinalizedBudgets.count(vote.nBudgetHash)) { + if (pfrom) { + // only ask for missing items after our syncing process is complete -- + // otherwise we'll think a full sync succeeded when they return a result + if (!zeronodeSync.IsSynced()) return false; + + LogPrint("zeronode","CBudgetManager::UpdateFinalizedBudget - Unknown Finalized Proposal %s, asking for source budget\n", vote.nBudgetHash.ToString()); + mapOrphanFinalizedBudgetVotes[vote.nBudgetHash] = vote; + + if (!askedForSourceProposalOrBudget.count(vote.nBudgetHash)) { + pfrom->PushMessage("znvs", vote.nBudgetHash); + askedForSourceProposalOrBudget[vote.nBudgetHash] = GetTime(); + } + } + + strError = "Finalized Budget " + vote.nBudgetHash.ToString() + " not found!"; + return false; + } + LogPrint("zeronode","CBudgetManager::UpdateFinalizedBudget - Finalized Proposal %s added\n", vote.nBudgetHash.ToString()); + return mapFinalizedBudgets[vote.nBudgetHash].AddOrUpdateVote(vote, strError); +} + +CBudgetProposal::CBudgetProposal() +{ + strProposalName = "unknown"; + nBlockStart = 0; + nBlockEnd = 0; + nAmount = 0; + nTime = 0; + fValid = true; +} + +CBudgetProposal::CBudgetProposal(std::string strProposalNameIn, std::string strURLIn, int nBlockStartIn, int nBlockEndIn, CScript addressIn, CAmount nAmountIn, uint256 nFeeTXHashIn) +{ + strProposalName = strProposalNameIn; + strURL = strURLIn; + nBlockStart = nBlockStartIn; + nBlockEnd = nBlockEndIn; + address = addressIn; + nAmount = nAmountIn; + nFeeTXHash = nFeeTXHashIn; + fValid = true; +} + +CBudgetProposal::CBudgetProposal(const CBudgetProposal& other) +{ + strProposalName = other.strProposalName; + strURL = other.strURL; + nBlockStart = other.nBlockStart; + nBlockEnd = other.nBlockEnd; + address = other.address; + nAmount = other.nAmount; + nTime = other.nTime; + nFeeTXHash = other.nFeeTXHash; + mapVotes = other.mapVotes; + fValid = true; +} + +bool CBudgetProposal::IsValid(std::string& strError, bool fCheckCollateral) +{ + if (GetNays() - GetYeas() > znodeman.CountEnabled(ActiveProtocol()) / 10) { + strError = "Proposal " + strProposalName + ": Active removal"; + return false; + } + + if (nBlockStart < 0) { + strError = "Invalid Proposal"; + return false; + } + + if (nBlockEnd < nBlockStart) { + strError = "Proposal " + strProposalName + ": Invalid nBlockEnd (end before start)"; + return false; + } + + if (nAmount < 10 * COIN) { + strError = "Proposal " + strProposalName + ": Invalid nAmount"; + return false; + } + + if (address == CScript()) { + strError = "Proposal " + strProposalName + ": Invalid Payment Address"; + return false; + } + + if (fCheckCollateral) { + int nConf = 0; + if (!IsBudgetCollateralValid(nFeeTXHash, GetHash(), strError, nTime, nConf)) { + strError = "Proposal " + strProposalName + ": Invalid collateral"; + return false; + } + } + + /* + TODO: There might be an issue with multisig in the coinbase on mainnet, we will add support for it in a future release. + */ + if (address.IsPayToScriptHash()) { + strError = "Proposal " + strProposalName + ": Multisig is not currently supported."; + return false; + } + + //if proposal doesn't gain traction within 2 weeks, remove it + // nTime not being saved correctly + // -- TODO: We should keep track of the last time the proposal was valid, if it's invalid for 2 weeks, erase it + // if(nTime + (60*60*24*2) < GetAdjustedTime()) { + // if(GetYeas()-GetNays() < (znodeman.CountEnabled(ActiveProtocol())/10)) { + // strError = "Not enough support"; + // return false; + // } + // } + + //can only pay out 10% of the possible coins (min value of coins) + if (nAmount > budget.GetTotalBudget(nBlockStart)) { + strError = "Proposal " + strProposalName + ": Payment more than max"; + return false; + } + + CBlockIndex* pindexPrev = chainActive.Tip(); + if (pindexPrev == NULL) { + strError = "Proposal " + strProposalName + ": Tip is NULL"; + return true; + } + + // Calculate maximum block this proposal will be valid, which is start of proposal + (number of payments * cycle) + int nProposalEnd = GetBlockStart() + (GetBudgetPaymentCycleBlocks() * GetTotalPaymentCount()); + + // if (GetBlockEnd() < pindexPrev->nHeight - GetBudgetPaymentCycleBlocks() / 2) { + if(nProposalEnd < pindexPrev->nHeight){ + strError = "Proposal " + strProposalName + ": Invalid nBlockEnd (" + std::to_string(nProposalEnd) + ") < current height (" + std::to_string(pindexPrev->nHeight) + ")"; + return false; + } + + return true; +} + +bool CBudgetProposal::AddOrUpdateVote(CBudgetVote& vote, std::string& strError) +{ + std::string strAction = "New vote inserted:"; + LOCK(cs); + + uint256 hash = vote.vin.prevout.GetHash(); + + if (mapVotes.count(hash)) { + if (mapVotes[hash].nTime > vote.nTime) { + strError = strprintf("new vote older than existing vote - %s\n", vote.GetHash().ToString()); + LogPrint("znbudget", "CBudgetProposal::AddOrUpdateVote - %s\n", strError); + return false; + } + if (vote.nTime - mapVotes[hash].nTime < BUDGET_VOTE_UPDATE_MIN) { + strError = strprintf("time between votes is too soon - %s - %lli sec < %lli sec\n", vote.GetHash().ToString(), vote.nTime - mapVotes[hash].nTime,BUDGET_VOTE_UPDATE_MIN); + LogPrint("znbudget", "CBudgetProposal::AddOrUpdateVote - %s\n", strError); + return false; + } + strAction = "Existing vote updated:"; + } + + if (vote.nTime > GetTime() + (60 * 60)) { + strError = strprintf("new vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n", vote.GetHash().ToString(), vote.nTime, GetTime() + (60 * 60)); + LogPrint("znbudget", "CBudgetProposal::AddOrUpdateVote - %s\n", strError); + return false; + } + + mapVotes[hash] = vote; + LogPrint("znbudget", "CBudgetProposal::AddOrUpdateVote - %s %s\n", strAction.c_str(), vote.GetHash().ToString().c_str()); + + return true; +} + +// If zeronode voted for a proposal, but is now invalid -- remove the vote +void CBudgetProposal::CleanAndRemove(bool fSignatureCheck) +{ + std::map::iterator it = mapVotes.begin(); + + while (it != mapVotes.end()) { + (*it).second.fValid = (*it).second.SignatureValid(fSignatureCheck); + ++it; + } +} + +double CBudgetProposal::GetRatio() +{ + int yeas = 0; + int nays = 0; + + std::map::iterator it = mapVotes.begin(); + + while (it != mapVotes.end()) { + if ((*it).second.nVote == VOTE_YES) yeas++; + if ((*it).second.nVote == VOTE_NO) nays++; + ++it; + } + + if (yeas + nays == 0) return 0.0f; + + return ((double)(yeas) / (double)(yeas + nays)); +} + +int CBudgetProposal::GetYeas() +{ + int ret = 0; + + std::map::iterator it = mapVotes.begin(); + while (it != mapVotes.end()) { + if ((*it).second.nVote == VOTE_YES && (*it).second.fValid) ret++; + ++it; + } + + return ret; +} + +int CBudgetProposal::GetNays() +{ + int ret = 0; + + std::map::iterator it = mapVotes.begin(); + while (it != mapVotes.end()) { + if ((*it).second.nVote == VOTE_NO && (*it).second.fValid) ret++; + ++it; + } + + return ret; +} + +int CBudgetProposal::GetAbstains() +{ + int ret = 0; + + std::map::iterator it = mapVotes.begin(); + while (it != mapVotes.end()) { + if ((*it).second.nVote == VOTE_ABSTAIN && (*it).second.fValid) ret++; + ++it; + } + + return ret; +} + +int CBudgetProposal::GetBlockStartCycle() +{ + //end block is half way through the next cycle (so the proposal will be removed much after the payment is sent) + + return nBlockStart - nBlockStart % GetBudgetPaymentCycleBlocks(); +} + +int CBudgetProposal::GetBlockCurrentCycle() +{ + CBlockIndex* pindexPrev = chainActive.Tip(); + if (pindexPrev == NULL) return -1; + + if (pindexPrev->nHeight >= GetBlockEndCycle()) return -1; + + return pindexPrev->nHeight - pindexPrev->nHeight % GetBudgetPaymentCycleBlocks(); +} + +int CBudgetProposal::GetBlockEndCycle() +{ + // XX42: right now single payment proposals have nBlockEnd have a cycle too early! + // switch back if it break something else + //end block is half way through the next cycle (so the proposal will be removed much after the payment is sent) + // return nBlockEnd - GetBudgetPaymentCycleBlocks() / 2; + + // End block is half way through the next cycle (so the proposal will be removed much after the payment is sent) + return nBlockEnd; + +} + +int CBudgetProposal::GetTotalPaymentCount() +{ + return (GetBlockEndCycle() - GetBlockStartCycle()) / GetBudgetPaymentCycleBlocks(); +} + +int CBudgetProposal::GetRemainingPaymentCount() +{ + // If this budget starts in the future, this value will be wrong + int nPayments = (GetBlockEndCycle() - GetBlockCurrentCycle()) / GetBudgetPaymentCycleBlocks() - 1; + // Take the lowest value + return std::min(nPayments, GetTotalPaymentCount()); +} + +CBudgetProposalBroadcast::CBudgetProposalBroadcast(std::string strProposalNameIn, std::string strURLIn, int nPaymentCount, CScript addressIn, CAmount nAmountIn, int nBlockStartIn, uint256 nFeeTXHashIn) +{ + strProposalName = strProposalNameIn; + strURL = strURLIn; + + nBlockStart = nBlockStartIn; + + int nCycleStart = nBlockStart - nBlockStart % GetBudgetPaymentCycleBlocks(); + + // XX42: right now single payment proposals have nBlockEnd have a cycle too early! + // switch back if it break something else + //calculate the end of the cycle for this vote, add half a cycle (vote will be deleted after that block) + // nBlockEnd = nCycleStart + GetBudgetPaymentCycleBlocks() * nPaymentCount + GetBudgetPaymentCycleBlocks() / 2; + + // Calculate the end of the cycle for this vote, vote will be deleted after next cycle + nBlockEnd = nCycleStart + (GetBudgetPaymentCycleBlocks() + 1) * nPaymentCount; + + address = addressIn; + nAmount = nAmountIn; + + nFeeTXHash = nFeeTXHashIn; +} + +void CBudgetProposalBroadcast::Relay() +{ + CInv inv(MSG_BUDGET_PROPOSAL, GetHash()); + RelayInv(inv); +} + +CBudgetVote::CBudgetVote() +{ + vin = CTxIn(); + nProposalHash = uint256(); + nVote = VOTE_ABSTAIN; + nTime = 0; + fValid = true; + fSynced = false; +} + +CBudgetVote::CBudgetVote(CTxIn vinIn, uint256 nProposalHashIn, int nVoteIn) +{ + vin = vinIn; + nProposalHash = nProposalHashIn; + nVote = nVoteIn; + nTime = GetAdjustedTime(); + fValid = true; + fSynced = false; +} + +void CBudgetVote::Relay() +{ + CInv inv(MSG_BUDGET_VOTE, GetHash()); + RelayInv(inv); +} + +bool CBudgetVote::Sign(CKey& keyZeronode, CPubKey& pubKeyZeronode) +{ + // Choose coins to use + CPubKey pubKeyCollateralAddress; + CKey keyCollateralAddress; + + std::string errorMessage; + std::string strMessage = vin.prevout.ToStringShort() + nProposalHash.ToString() + boost::lexical_cast(nVote) + boost::lexical_cast(nTime); + + if (!obfuScationSigner.SignMessage(strMessage, errorMessage, vchSig, keyZeronode)) { + LogPrint("zeronode","CBudgetVote::Sign - Error upon calling SignMessage"); + return false; + } + + if (!obfuScationSigner.VerifyMessage(pubKeyZeronode, vchSig, strMessage, errorMessage)) { + LogPrint("zeronode","CBudgetVote::Sign - Error upon calling VerifyMessage"); + return false; + } + + return true; +} + +bool CBudgetVote::SignatureValid(bool fSignatureCheck) +{ + std::string errorMessage; + std::string strMessage = vin.prevout.ToStringShort() + nProposalHash.ToString() + boost::lexical_cast(nVote) + boost::lexical_cast(nTime); + + CZeronode* pzn = znodeman.Find(vin); + + if (pzn == NULL) { + if (fDebug){ + LogPrint("zeronode","CBudgetVote::SignatureValid() - Unknown Zeronode - %s\n", vin.prevout.hash.ToString()); + } + return false; + } + + if (!fSignatureCheck) return true; + + if (!obfuScationSigner.VerifyMessage(pzn->pubKeyZeronode, vchSig, strMessage, errorMessage)) { + LogPrint("zeronode","CBudgetVote::SignatureValid() - Verify message failed\n"); + return false; + } + + return true; +} + +CFinalizedBudget::CFinalizedBudget() +{ + strBudgetName = ""; + nBlockStart = 0; + vecBudgetPayments.clear(); + mapVotes.clear(); + nFeeTXHash = uint256(); + nTime = 0; + fValid = true; + fAutoChecked = false; +} + +CFinalizedBudget::CFinalizedBudget(const CFinalizedBudget& other) +{ + strBudgetName = other.strBudgetName; + nBlockStart = other.nBlockStart; + vecBudgetPayments = other.vecBudgetPayments; + mapVotes = other.mapVotes; + nFeeTXHash = other.nFeeTXHash; + nTime = other.nTime; + fValid = true; + fAutoChecked = false; +} + +bool CFinalizedBudget::AddOrUpdateVote(CFinalizedBudgetVote& vote, std::string& strError) +{ + LOCK(cs); + + uint256 hash = vote.vin.prevout.GetHash(); + std::string strAction = "New vote inserted:"; + + if (mapVotes.count(hash)) { + if (mapVotes[hash].nTime > vote.nTime) { + strError = strprintf("new vote older than existing vote - %s\n", vote.GetHash().ToString()); + LogPrint("znbudget", "CFinalizedBudget::AddOrUpdateVote - %s\n", strError); + return false; + } + if (vote.nTime - mapVotes[hash].nTime < BUDGET_VOTE_UPDATE_MIN) { + strError = strprintf("time between votes is too soon - %s - %lli sec < %lli sec\n", vote.GetHash().ToString(), vote.nTime - mapVotes[hash].nTime,BUDGET_VOTE_UPDATE_MIN); + LogPrint("znbudget", "CFinalizedBudget::AddOrUpdateVote - %s\n", strError); + return false; + } + strAction = "Existing vote updated:"; + } + + if (vote.nTime > GetTime() + (60 * 60)) { + strError = strprintf("new vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n", vote.GetHash().ToString(), vote.nTime, GetTime() + (60 * 60)); + LogPrint("znbudget", "CFinalizedBudget::AddOrUpdateVote - %s\n", strError); + return false; + } + + mapVotes[hash] = vote; + LogPrint("znbudget", "CFinalizedBudget::AddOrUpdateVote - %s %s\n", strAction.c_str(), vote.GetHash().ToString().c_str()); + return true; +} + +//evaluate if we should vote for this. Zeronode only +void CFinalizedBudget::AutoCheck() +{ + LOCK(cs); + + CBlockIndex* pindexPrev = chainActive.Tip(); + if (!pindexPrev) return; + + LogPrint("zeronode","CFinalizedBudget::AutoCheck - %lli - %d\n", pindexPrev->nHeight, fAutoChecked); + + if (!fZeroNode || fAutoChecked) return; + + //do this 1 in 4 blocks -- spread out the voting activity on mainnet + // -- this function is only called every fourteenth block, so this is really 1 in 56 blocks + if (NetworkIdFromCommandLine() == CBaseChainParams::MAIN && rand() % 4 != 0) { + LogPrint("zeronode","CFinalizedBudget::AutoCheck - waiting\n"); + return; + } + + fAutoChecked = true; //we only need to check this once + + + if (strBudgetMode == "auto") //only vote for exact matches + { + std::vector vBudgetProposals = budget.GetBudget(); + + + for (unsigned int i = 0; i < vecBudgetPayments.size(); i++) { + LogPrint("zeronode","CFinalizedBudget::AutoCheck - nProp %d %s\n", i, vecBudgetPayments[i].nProposalHash.ToString()); + LogPrint("zeronode","CFinalizedBudget::AutoCheck - Payee %d %s\n", i, EncodeDestination(vecBudgetPayments[i].payee)); + LogPrint("zeronode","CFinalizedBudget::AutoCheck - nAmount %d %lli\n", i, vecBudgetPayments[i].nAmount); + } + + for (unsigned int i = 0; i < vBudgetProposals.size(); i++) { + LogPrint("zeronode","CFinalizedBudget::AutoCheck - nProp %d %s\n", i, vBudgetProposals[i]->GetHash().ToString()); + LogPrint("zeronode","CFinalizedBudget::AutoCheck - Payee %d %s\n", i, EncodeDestination(vBudgetProposals[i]->GetPayee())); + LogPrint("zeronode","CFinalizedBudget::AutoCheck - nAmount %d %lli\n", i, vBudgetProposals[i]->GetAmount()); + } + + if (vBudgetProposals.size() == 0) { + LogPrint("zeronode","CFinalizedBudget::AutoCheck - Can't get Budget, aborting\n"); + return; + } + + if (vBudgetProposals.size() != vecBudgetPayments.size()) { + LogPrint("zeronode","CFinalizedBudget::AutoCheck - Budget length doesn't match. vBudgetProposals.size()=%ld != vecBudgetPayments.size()=%ld\n", + vBudgetProposals.size(), vecBudgetPayments.size()); + return; + } + + + for (unsigned int i = 0; i < vecBudgetPayments.size(); i++) { + if (i > vBudgetProposals.size() - 1) { + LogPrint("zeronode","CFinalizedBudget::AutoCheck - Proposal size mismatch, i=%d > (vBudgetProposals.size() - 1)=%d\n", i, vBudgetProposals.size() - 1); + return; + } + + if (vecBudgetPayments[i].nProposalHash != vBudgetProposals[i]->GetHash()) { + LogPrint("zeronode","CFinalizedBudget::AutoCheck - item #%d doesn't match %s %s\n", i, vecBudgetPayments[i].nProposalHash.ToString(), vBudgetProposals[i]->GetHash().ToString()); + return; + } + + // if(vecBudgetPayments[i].payee != vBudgetProposals[i]->GetPayee()){ -- triggered with false positive + if (EncodeDestination(vecBudgetPayments[i].payee) != EncodeDestination(vBudgetProposals[i]->GetPayee())) { + LogPrint("zeronode","CFinalizedBudget::AutoCheck - item #%d payee doesn't match %s %s\n", i, EncodeDestination(vecBudgetPayments[i].payee), EncodeDestination(vBudgetProposals[i]->GetPayee())); + return; + } + + if (vecBudgetPayments[i].nAmount != vBudgetProposals[i]->GetAmount()) { + LogPrint("zeronode","CFinalizedBudget::AutoCheck - item #%d payee doesn't match %lli %lli\n", i, vecBudgetPayments[i].nAmount, vBudgetProposals[i]->GetAmount()); + return; + } + } + + LogPrint("zeronode","CFinalizedBudget::AutoCheck - Finalized Budget Matches! Submitting Vote.\n"); + SubmitVote(); + } +} +// If zeronode voted for a proposal, but is now invalid -- remove the vote +void CFinalizedBudget::CleanAndRemove(bool fSignatureCheck) +{ + std::map::iterator it = mapVotes.begin(); + + while (it != mapVotes.end()) { + (*it).second.fValid = (*it).second.SignatureValid(fSignatureCheck); + ++it; + } +} + + +CAmount CFinalizedBudget::GetTotalPayout() +{ + CAmount ret = 0; + + for (unsigned int i = 0; i < vecBudgetPayments.size(); i++) { + ret += vecBudgetPayments[i].nAmount; + } + + return ret; +} + +std::string CFinalizedBudget::GetProposals() +{ + LOCK(cs); + std::string ret = ""; + + BOOST_FOREACH (CTxBudgetPayment& budgetPayment, vecBudgetPayments) { + CBudgetProposal* pbudgetProposal = budget.FindProposal(budgetPayment.nProposalHash); + + std::string token = budgetPayment.nProposalHash.ToString(); + + if (pbudgetProposal) token = pbudgetProposal->GetName(); + if (ret == "") { + ret = token; + } else { + ret += "," + token; + } + } + return ret; +} + +std::string CFinalizedBudget::GetStatus() +{ + std::string retBadHashes = ""; + std::string retBadPayeeOrAmount = ""; + + for (int nBlockHeight = GetBlockStart(); nBlockHeight <= GetBlockEnd(); nBlockHeight++) { + CTxBudgetPayment budgetPayment; + if (!GetBudgetPaymentByBlock(nBlockHeight, budgetPayment)) { + LogPrint("zeronode","CFinalizedBudget::GetStatus - Couldn't find budget payment for block %lld\n", nBlockHeight); + continue; + } + + CBudgetProposal* pbudgetProposal = budget.FindProposal(budgetPayment.nProposalHash); + if (!pbudgetProposal) { + if (retBadHashes == "") { + retBadHashes = "Unknown proposal hash! Check this proposal before voting" + budgetPayment.nProposalHash.ToString(); + } else { + retBadHashes += "," + budgetPayment.nProposalHash.ToString(); + } + } else { + if (pbudgetProposal->GetPayee() != budgetPayment.payee || pbudgetProposal->GetAmount() != budgetPayment.nAmount) { + if (retBadPayeeOrAmount == "") { + retBadPayeeOrAmount = "Budget payee/nAmount doesn't match our proposal! " + budgetPayment.nProposalHash.ToString(); + } else { + retBadPayeeOrAmount += "," + budgetPayment.nProposalHash.ToString(); + } + } + } + } + + if (retBadHashes == "" && retBadPayeeOrAmount == "") return "OK"; + + return retBadHashes + retBadPayeeOrAmount; +} + +bool CFinalizedBudget::IsValid(std::string& strError, bool fCheckCollateral) +{ + // Must be the correct block for payment to happen (once a month) + if (nBlockStart % GetBudgetPaymentCycleBlocks() != 0) { + strError = "Invalid BlockStart"; + return false; + } + + // The following 2 checks check the same (basically if vecBudgetPayments.size() > 100) + if (GetBlockEnd() - nBlockStart > 100) { + strError = "Invalid BlockEnd"; + return false; + } + if ((int)vecBudgetPayments.size() > 100) { + strError = "Invalid budget payments count (too many)"; + return false; + } + if (strBudgetName == "") { + strError = "Invalid Budget Name"; + return false; + } + if (nBlockStart == 0) { + strError = "Budget " + strBudgetName + " Invalid BlockStart == 0"; + return false; + } + if (nFeeTXHash == uint256()) { + strError = "Budget " + strBudgetName + " Invalid FeeTx == 0"; + return false; + } + + // Can only pay out 10% of the possible coins (min value of coins) + if (GetTotalPayout() > budget.GetTotalBudget(nBlockStart)) { + strError = "Budget " + strBudgetName + " Invalid Payout (more than max)"; + return false; + } + + std::string strError2 = ""; + if (fCheckCollateral) { + int nConf = 0; + if (!IsBudgetCollateralValid(nFeeTXHash, GetHash(), strError2, nTime, nConf)) { + { + strError = "Budget " + strBudgetName + " Invalid Collateral : " + strError2; + return false; + } + } + } + + //TODO: if N cycles old, invalid, invalid + + CBlockIndex* pindexPrev = chainActive.Tip(); + if (pindexPrev == NULL) return true; + +// TODO: verify if we can safely remove this +// +// if (nBlockStart < pindexPrev->nHeight - 100) { +// strError = "Budget " + strBudgetName + " Older than current blockHeight" ; +// return false; +// } + + return true; +} + +bool CFinalizedBudget::IsTransactionValid(const CTransaction& txNew, int nBlockHeight) +{ + int nCurrentBudgetPayment = nBlockHeight - GetBlockStart(); + if (nCurrentBudgetPayment < 0) { + LogPrint("zeronode","CFinalizedBudget::IsTransactionValid - Invalid block - height: %d start: %d\n", nBlockHeight, GetBlockStart()); + return false; + } + + if (nCurrentBudgetPayment > (int)vecBudgetPayments.size() - 1) { + LogPrint("zeronode","CFinalizedBudget::IsTransactionValid - Invalid block - current budget payment: %d of %d\n", nCurrentBudgetPayment + 1, (int)vecBudgetPayments.size()); + return false; + } + + bool found = false; + BOOST_FOREACH (CTxOut out, txNew.vout) { + if (vecBudgetPayments[nCurrentBudgetPayment].payee == out.scriptPubKey && vecBudgetPayments[nCurrentBudgetPayment].nAmount == out.nValue) { + found = true; + //LogPrint("zeronode","CFinalizedBudget::IsTransactionValid - Found valid Budget Payment of %d for %d\n", + // vecBudgetPayments[nCurrentBudgetPayment].nAmount, vecBudgetPayments[nCurrentBudgetPayment].nProposalHash.Get32()); + } + } + + if (!found) { + CTxDestination address1; + ExtractDestination(vecBudgetPayments[nCurrentBudgetPayment].payee, address1); + + LogPrint("zeronode","CFinalizedBudget::IsTransactionValid - Missing required payment - %s: %d c: %d\n", + EncodeDestination(address1), vecBudgetPayments[nCurrentBudgetPayment].nAmount, nCurrentBudgetPayment); + } + + return found; +} + +void CFinalizedBudget::SubmitVote() +{ + CPubKey pubKeyZeronode; + CKey keyZeronode; + std::string errorMessage; + + if (!obfuScationSigner.SetKey(strZeroNodePrivKey, errorMessage, keyZeronode, pubKeyZeronode)) { + LogPrint("zeronode","CFinalizedBudget::SubmitVote - Error upon calling SetKey\n"); + return; + } + + CFinalizedBudgetVote vote(activeZeronode.vin, GetHash()); + if (!vote.Sign(keyZeronode, pubKeyZeronode)) { + LogPrint("zeronode","CFinalizedBudget::SubmitVote - Failure to sign."); + return; + } + + std::string strError = ""; + if (budget.UpdateFinalizedBudget(vote, NULL, strError)) { + LogPrint("zeronode","CFinalizedBudget::SubmitVote - new finalized budget vote - %s\n", vote.GetHash().ToString()); + + budget.mapSeenFinalizedBudgetVotes.insert(make_pair(vote.GetHash(), vote)); + vote.Relay(); + } else { + LogPrint("zeronode","CFinalizedBudget::SubmitVote : Error submitting vote - %s\n", strError); + } +} + +CFinalizedBudgetBroadcast::CFinalizedBudgetBroadcast() +{ + strBudgetName = ""; + nBlockStart = 0; + vecBudgetPayments.clear(); + mapVotes.clear(); + vchSig.clear(); + nFeeTXHash = uint256(); +} + +CFinalizedBudgetBroadcast::CFinalizedBudgetBroadcast(const CFinalizedBudget& other) +{ + strBudgetName = other.strBudgetName; + nBlockStart = other.nBlockStart; + BOOST_FOREACH (CTxBudgetPayment out, other.vecBudgetPayments) + vecBudgetPayments.push_back(out); + mapVotes = other.mapVotes; + nFeeTXHash = other.nFeeTXHash; +} + +CFinalizedBudgetBroadcast::CFinalizedBudgetBroadcast(std::string strBudgetNameIn, int nBlockStartIn, std::vector vecBudgetPaymentsIn, uint256 nFeeTXHashIn) +{ + strBudgetName = strBudgetNameIn; + nBlockStart = nBlockStartIn; + BOOST_FOREACH (CTxBudgetPayment out, vecBudgetPaymentsIn) + vecBudgetPayments.push_back(out); + mapVotes.clear(); + nFeeTXHash = nFeeTXHashIn; +} + +void CFinalizedBudgetBroadcast::Relay() +{ + CInv inv(MSG_BUDGET_FINALIZED, GetHash()); + RelayInv(inv); +} + +CFinalizedBudgetVote::CFinalizedBudgetVote() +{ + vin = CTxIn(); + nBudgetHash = uint256(); + nTime = 0; + vchSig.clear(); + fValid = true; + fSynced = false; +} + +CFinalizedBudgetVote::CFinalizedBudgetVote(CTxIn vinIn, uint256 nBudgetHashIn) +{ + vin = vinIn; + nBudgetHash = nBudgetHashIn; + nTime = GetAdjustedTime(); + vchSig.clear(); + fValid = true; + fSynced = false; +} + +void CFinalizedBudgetVote::Relay() +{ + CInv inv(MSG_BUDGET_FINALIZED_VOTE, GetHash()); + RelayInv(inv); +} + +bool CFinalizedBudgetVote::Sign(CKey& keyZeronode, CPubKey& pubKeyZeronode) +{ + // Choose coins to use + CPubKey pubKeyCollateralAddress; + CKey keyCollateralAddress; + + std::string errorMessage; + std::string strMessage = vin.prevout.ToStringShort() + nBudgetHash.ToString() + boost::lexical_cast(nTime); + + if (!obfuScationSigner.SignMessage(strMessage, errorMessage, vchSig, keyZeronode)) { + LogPrint("zeronode","CFinalizedBudgetVote::Sign - Error upon calling SignMessage"); + return false; + } + + if (!obfuScationSigner.VerifyMessage(pubKeyZeronode, vchSig, strMessage, errorMessage)) { + LogPrint("zeronode","CFinalizedBudgetVote::Sign - Error upon calling VerifyMessage"); + return false; + } + + return true; +} + +bool CFinalizedBudgetVote::SignatureValid(bool fSignatureCheck) +{ + std::string errorMessage; + + std::string strMessage = vin.prevout.ToStringShort() + nBudgetHash.ToString() + boost::lexical_cast(nTime); + + CZeronode* pzn = znodeman.Find(vin); + + if (pzn == NULL) { + LogPrint("zeronode","CFinalizedBudgetVote::SignatureValid() - Unknown Zeronode %s\n", strMessage); + return false; + } + + if (!fSignatureCheck) return true; + + if (!obfuScationSigner.VerifyMessage(pzn->pubKeyZeronode, vchSig, strMessage, errorMessage)) { + LogPrint("zeronode","CFinalizedBudgetVote::SignatureValid() - Verify message failed %s %s\n", strMessage, errorMessage); + return false; + } + + return true; +} + +std::string CBudgetManager::ToString() const +{ + std::ostringstream info; + + info << "Proposals: " << (int)mapProposals.size() << ", Budgets: " << (int)mapFinalizedBudgets.size() << ", Seen Budgets: " << (int)mapSeenZeronodeBudgetProposals.size() << ", Seen Budget Votes: " << (int)mapSeenZeronodeBudgetVotes.size() << ", Seen Final Budgets: " << (int)mapSeenFinalizedBudgets.size() << ", Seen Final Budget Votes: " << (int)mapSeenFinalizedBudgetVotes.size(); + + return info.str(); +} diff --git a/src/zeronode/budget.h b/src/zeronode/budget.h new file mode 100644 index 00000000000..e940485cc51 --- /dev/null +++ b/src/zeronode/budget.h @@ -0,0 +1,608 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef ZERONODE_BUDGET_H +#define ZERONODE_BUDGET_H + +#include "base58.h" +#include "init.h" +#include "key.h" +#include "main.h" +#include "zeronode/zeronode.h" +#include "net.h" +#include "sync.h" +#include "util.h" +#include + +using namespace std; + +extern CCriticalSection cs_budget; + +class CBudgetManager; +class CFinalizedBudgetBroadcast; +class CFinalizedBudget; +class CBudgetProposal; +class CBudgetProposalBroadcast; +class CTxBudgetPayment; + +#define VOTE_ABSTAIN 0 +#define VOTE_YES 1 +#define VOTE_NO 2 + +static const CAmount PROPOSAL_FEE_TX = (50 * COIN); +static const CAmount BUDGET_FEE_TX = (50 * COIN); +static const int64_t BUDGET_VOTE_UPDATE_MIN = 60 * 60; + +extern std::vector vecImmatureBudgetProposals; +extern std::vector vecImmatureFinalizedBudgets; + +extern CBudgetManager budget; +void DumpBudgets(); + +// Define amount of blocks in budget payment cycle +int GetBudgetPaymentCycleBlocks(); + +//Check the collateral transaction for the budget proposal/finalized budget +bool IsBudgetCollateralValid(uint256 nTxCollateralHash, uint256 nExpectedHash, std::string& strError, int64_t& nTime, int& nConf); + +// +// CBudgetVote - Allow a zeronode node to vote and broadcast throughout the network +// + +class CBudgetVote +{ +public: + bool fValid; //if the vote is currently valid / counted + bool fSynced; //if we've sent this to our peers + CTxIn vin; + uint256 nProposalHash; + int nVote; + int64_t nTime; + std::vector vchSig; + + CBudgetVote(); + CBudgetVote(CTxIn vin, uint256 nProposalHash, int nVoteIn); + + bool Sign(CKey& keyZeronode, CPubKey& pubKeyZeronode); + bool SignatureValid(bool fSignatureCheck); + void Relay(); + + std::string GetVoteString() + { + std::string ret = "ABSTAIN"; + if (nVote == VOTE_YES) ret = "YES"; + if (nVote == VOTE_NO) ret = "NO"; + return ret; + } + + uint256 GetHash() + { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << vin; + ss << nProposalHash; + ss << nVote; + ss << nTime; + return ss.GetHash(); + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(vin); + READWRITE(nProposalHash); + READWRITE(nVote); + READWRITE(nTime); + READWRITE(vchSig); + } +}; + +// +// CFinalizedBudgetVote - Allow a zeronode node to vote and broadcast throughout the network +// + +class CFinalizedBudgetVote +{ +public: + bool fValid; //if the vote is currently valid / counted + bool fSynced; //if we've sent this to our peers + CTxIn vin; + uint256 nBudgetHash; + int64_t nTime; + std::vector vchSig; + + CFinalizedBudgetVote(); + CFinalizedBudgetVote(CTxIn vinIn, uint256 nBudgetHashIn); + + bool Sign(CKey& keyZeronode, CPubKey& pubKeyZeronode); + bool SignatureValid(bool fSignatureCheck); + void Relay(); + + uint256 GetHash() + { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << vin; + ss << nBudgetHash; + ss << nTime; + return ss.GetHash(); + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(vin); + READWRITE(nBudgetHash); + READWRITE(nTime); + READWRITE(vchSig); + } +}; + +/** Save Budget Manager (budget.dat) + */ +class CBudgetDB +{ +private: + boost::filesystem::path pathDB; + std::string strMagicMessage; + +public: + enum ReadResult { + Ok, + FileError, + HashReadError, + IncorrectHash, + IncorrectMagicMessage, + IncorrectMagicNumber, + IncorrectFormat + }; + + CBudgetDB(); + bool Write(const CBudgetManager& objToSave); + ReadResult Read(CBudgetManager& objToLoad, bool fDryRun = false); +}; + + +// +// Budget Manager : Contains all proposals for the budget +// +class CBudgetManager +{ +private: + //hold txes until they mature enough to use + // XX42 map mapCollateral; + map mapCollateralTxids; + +public: + // critical section to protect the inner data structures + mutable CCriticalSection cs; + + // keep track of the scanning errors I've seen + map mapProposals; + map mapFinalizedBudgets; + + std::map mapSeenZeronodeBudgetProposals; + std::map mapSeenZeronodeBudgetVotes; + std::map mapOrphanZeronodeBudgetVotes; + std::map mapSeenFinalizedBudgets; + std::map mapSeenFinalizedBudgetVotes; + std::map mapOrphanFinalizedBudgetVotes; + + CBudgetManager() + { + mapProposals.clear(); + mapFinalizedBudgets.clear(); + } + + void ClearSeen() + { + mapSeenZeronodeBudgetProposals.clear(); + mapSeenZeronodeBudgetVotes.clear(); + mapSeenFinalizedBudgets.clear(); + mapSeenFinalizedBudgetVotes.clear(); + } + + int sizeFinalized() { return (int)mapFinalizedBudgets.size(); } + int sizeProposals() { return (int)mapProposals.size(); } + + void ResetSync(); + void MarkSynced(); + void Sync(CNode* node, uint256 nProp, bool fPartial = false); + + void Calculate(); + void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); + void NewBlock(); + CBudgetProposal* FindProposal(const std::string& strProposalName); + CBudgetProposal* FindProposal(uint256 nHash); + CFinalizedBudget* FindFinalizedBudget(uint256 nHash); + std::pair GetVotes(std::string strProposalName); + + CAmount GetTotalBudget(int nHeight); + std::vector GetBudget(); + std::vector GetAllProposals(); + std::vector GetFinalizedBudgets(); + bool IsBudgetPaymentBlock(int nBlockHeight); + bool AddProposal(CBudgetProposal& budgetProposal); + bool AddFinalizedBudget(CFinalizedBudget& finalizedBudget); + void SubmitFinalBudget(); + + bool UpdateProposal(CBudgetVote& vote, CNode* pfrom, std::string& strError); + bool UpdateFinalizedBudget(CFinalizedBudgetVote& vote, CNode* pfrom, std::string& strError); + bool PropExists(uint256 nHash); + bool IsTransactionValid(const CTransaction& txNew, int nBlockHeight); + std::string GetRequiredPaymentsString(int nBlockHeight); + void FillBlockPayee(CMutableTransaction& txNew, CAmount nFees, CTxOut& txFounders, CTxOut& txZeronodes); + + void CheckOrphanVotes(); + void Clear() + { + LOCK(cs); + + LogPrintf("Budget object cleared\n"); + mapProposals.clear(); + mapFinalizedBudgets.clear(); + mapSeenZeronodeBudgetProposals.clear(); + mapSeenZeronodeBudgetVotes.clear(); + mapSeenFinalizedBudgets.clear(); + mapSeenFinalizedBudgetVotes.clear(); + mapOrphanZeronodeBudgetVotes.clear(); + mapOrphanFinalizedBudgetVotes.clear(); + } + void CheckAndRemove(); + std::string ToString() const; + + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(mapSeenZeronodeBudgetProposals); + READWRITE(mapSeenZeronodeBudgetVotes); + READWRITE(mapSeenFinalizedBudgets); + READWRITE(mapSeenFinalizedBudgetVotes); + READWRITE(mapOrphanZeronodeBudgetVotes); + READWRITE(mapOrphanFinalizedBudgetVotes); + + READWRITE(mapProposals); + READWRITE(mapFinalizedBudgets); + } +}; + + +class CTxBudgetPayment +{ +public: + uint256 nProposalHash; + CScript payee; + CAmount nAmount; + + CTxBudgetPayment() + { + payee = CScript(); + nAmount = 0; + nProposalHash = uint256(); + } + + ADD_SERIALIZE_METHODS; + + //for saving to the serialized db + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(*(CScriptBase*)(&payee)); + READWRITE(nAmount); + READWRITE(nProposalHash); + } +}; + +// +// Finalized Budget : Contains the suggested proposals to pay on a given block +// + +class CFinalizedBudget +{ +private: + // critical section to protect the inner data structures + mutable CCriticalSection cs; + bool fAutoChecked; //If it matches what we see, we'll auto vote for it (zeronode only) + +public: + bool fValid; + std::string strBudgetName; + int nBlockStart; + std::vector vecBudgetPayments; + map mapVotes; + uint256 nFeeTXHash; + int64_t nTime; + + CFinalizedBudget(); + CFinalizedBudget(const CFinalizedBudget& other); + + void CleanAndRemove(bool fSignatureCheck); + bool AddOrUpdateVote(CFinalizedBudgetVote& vote, std::string& strError); + double GetScore(); + bool HasMinimumRequiredSupport(); + + bool IsValid(std::string& strError, bool fCheckCollateral = true); + + std::string GetName() { return strBudgetName; } + std::string GetProposals(); + int GetBlockStart() { return nBlockStart; } + int GetBlockEnd() { return nBlockStart + (int)(vecBudgetPayments.size() - 1); } + int GetVoteCount() { return (int)mapVotes.size(); } + bool IsTransactionValid(const CTransaction& txNew, int nBlockHeight); + bool GetBudgetPaymentByBlock(int64_t nBlockHeight, CTxBudgetPayment& payment) + { + LOCK(cs); + + int i = nBlockHeight - GetBlockStart(); + if (i < 0) return false; + if (i > (int)vecBudgetPayments.size() - 1) return false; + payment = vecBudgetPayments[i]; + return true; + } + bool GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, CAmount& nAmount) + { + LOCK(cs); + + int i = nBlockHeight - GetBlockStart(); + if (i < 0) return false; + if (i > (int)vecBudgetPayments.size() - 1) return false; + payee = vecBudgetPayments[i].payee; + nAmount = vecBudgetPayments[i].nAmount; + return true; + } + + //check to see if we should vote on this + void AutoCheck(); + //total zero paid out by this budget + CAmount GetTotalPayout(); + //vote on this finalized budget as a zeronode + void SubmitVote(); + + //checks the hashes to make sure we know about them + string GetStatus(); + + uint256 GetHash() + { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << strBudgetName; + ss << nBlockStart; + ss << vecBudgetPayments; + + uint256 h1 = ss.GetHash(); + return h1; + } + + ADD_SERIALIZE_METHODS; + + //for saving to the serialized db + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(LIMITED_STRING(strBudgetName, 20)); + READWRITE(nFeeTXHash); + READWRITE(nTime); + READWRITE(nBlockStart); + READWRITE(vecBudgetPayments); + READWRITE(fAutoChecked); + + READWRITE(mapVotes); + } +}; + +// FinalizedBudget are cast then sent to peers with this object, which leaves the votes out +class CFinalizedBudgetBroadcast : public CFinalizedBudget +{ +private: + std::vector vchSig; + +public: + CFinalizedBudgetBroadcast(); + CFinalizedBudgetBroadcast(const CFinalizedBudget& other); + CFinalizedBudgetBroadcast(std::string strBudgetNameIn, int nBlockStartIn, std::vector vecBudgetPaymentsIn, uint256 nFeeTXHashIn); + + void swap(CFinalizedBudgetBroadcast& first, CFinalizedBudgetBroadcast& second) // nothrow + { + // enable ADL (not necessary in our case, but good practice) + using std::swap; + + // by swapping the members of two classes, + // the two classes are effectively swapped + swap(first.strBudgetName, second.strBudgetName); + swap(first.nBlockStart, second.nBlockStart); + first.mapVotes.swap(second.mapVotes); + first.vecBudgetPayments.swap(second.vecBudgetPayments); + swap(first.nFeeTXHash, second.nFeeTXHash); + swap(first.nTime, second.nTime); + } + + CFinalizedBudgetBroadcast& operator=(CFinalizedBudgetBroadcast from) + { + swap(*this, from); + return *this; + } + + void Relay(); + + ADD_SERIALIZE_METHODS; + + //for propagating messages + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + //for syncing with other clients + READWRITE(LIMITED_STRING(strBudgetName, 20)); + READWRITE(nBlockStart); + READWRITE(vecBudgetPayments); + READWRITE(nFeeTXHash); + } +}; + + +// +// Budget Proposal : Contains the zeronode votes for each budget +// + +class CBudgetProposal +{ +private: + // critical section to protect the inner data structures + mutable CCriticalSection cs; + CAmount nAlloted; + +public: + bool fValid; + std::string strProposalName; + + /* + json object with name, short-description, long-description, pdf-url and any other info + This allows the proposal website to stay 100% decentralized + */ + std::string strURL; + int nBlockStart; + int nBlockEnd; + CAmount nAmount; + CScript address; + int64_t nTime; + uint256 nFeeTXHash; + + map mapVotes; + //cache object + + CBudgetProposal(); + CBudgetProposal(const CBudgetProposal& other); + CBudgetProposal(std::string strProposalNameIn, std::string strURLIn, int nBlockStartIn, int nBlockEndIn, CScript addressIn, CAmount nAmountIn, uint256 nFeeTXHashIn); + + void Calculate(); + bool AddOrUpdateVote(CBudgetVote& vote, std::string& strError); + bool HasMinimumRequiredSupport(); + std::pair GetVotes(); + + bool IsValid(std::string& strError, bool fCheckCollateral = true); + + bool IsEstablished() + { + // Proposals must be at least a day old to make it into a budget + if (NetworkIdFromCommandLine() == CBaseChainParams::MAIN) return (nTime < GetTime() - (60 * 60 * 24)); + + // For testing purposes - 5 minutes + return (nTime < GetTime() - (60 * 5)); + } + + std::string GetName() { return strProposalName; } + std::string GetURL() { return strURL; } + int GetBlockStart() { return nBlockStart; } + int GetBlockEnd() { return nBlockEnd; } + CScript GetPayee() { return address; } + int GetTotalPaymentCount(); + int GetRemainingPaymentCount(); + int GetBlockStartCycle(); + int GetBlockCurrentCycle(); + int GetBlockEndCycle(); + double GetRatio(); + int GetYeas(); + int GetNays(); + int GetAbstains(); + CAmount GetAmount() { return nAmount; } + void SetAllotted(CAmount nAllotedIn) { nAlloted = nAllotedIn; } + CAmount GetAllotted() { return nAlloted; } + + void CleanAndRemove(bool fSignatureCheck); + + uint256 GetHash() + { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << strProposalName; + ss << strURL; + ss << nBlockStart; + ss << nBlockEnd; + ss << nAmount; + ss << *(CScriptBase*)(&address); + uint256 h1 = ss.GetHash(); + + return h1; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + //for syncing with other clients + READWRITE(LIMITED_STRING(strProposalName, 20)); + READWRITE(LIMITED_STRING(strURL, 64)); + READWRITE(nTime); + READWRITE(nBlockStart); + READWRITE(nBlockEnd); + READWRITE(nAmount); + READWRITE(*(CScriptBase*)(&address)); + READWRITE(nTime); + READWRITE(nFeeTXHash); + + //for saving to the serialized db + READWRITE(mapVotes); + } +}; + +// Proposals are cast then sent to peers with this object, which leaves the votes out +class CBudgetProposalBroadcast : public CBudgetProposal +{ +public: + CBudgetProposalBroadcast() : CBudgetProposal() {} + CBudgetProposalBroadcast(const CBudgetProposal& other) : CBudgetProposal(other) {} + CBudgetProposalBroadcast(const CBudgetProposalBroadcast& other) : CBudgetProposal(other) {} + CBudgetProposalBroadcast(std::string strProposalNameIn, std::string strURLIn, int nPaymentCount, CScript addressIn, CAmount nAmountIn, int nBlockStartIn, uint256 nFeeTXHashIn); + + void swap(CBudgetProposalBroadcast& first, CBudgetProposalBroadcast& second) // nothrow + { + // enable ADL (not necessary in our case, but good practice) + using std::swap; + + // by swapping the members of two classes, + // the two classes are effectively swapped + swap(first.strProposalName, second.strProposalName); + swap(first.nBlockStart, second.nBlockStart); + swap(first.strURL, second.strURL); + swap(first.nBlockEnd, second.nBlockEnd); + swap(first.nAmount, second.nAmount); + swap(first.address, second.address); + swap(first.nTime, second.nTime); + swap(first.nFeeTXHash, second.nFeeTXHash); + first.mapVotes.swap(second.mapVotes); + } + + CBudgetProposalBroadcast& operator=(CBudgetProposalBroadcast from) + { + swap(*this, from); + return *this; + } + + void Relay(); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + //for syncing with other clients + + READWRITE(LIMITED_STRING(strProposalName, 20)); + READWRITE(LIMITED_STRING(strURL, 64)); + READWRITE(nTime); + READWRITE(nBlockStart); + READWRITE(nBlockEnd); + READWRITE(nAmount); + READWRITE(*(CScriptBase*)(&address)); + READWRITE(nFeeTXHash); + } +}; + + +#endif diff --git a/src/zeronode/obfuscation.cpp b/src/zeronode/obfuscation.cpp new file mode 100644 index 00000000000..8ec3799c180 --- /dev/null +++ b/src/zeronode/obfuscation.cpp @@ -0,0 +1,158 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zeronode/obfuscation.h" +#include "coincontrol.h" +#include "core_io.h" +#include "init.h" +#include "main.h" +#include "zeronode/zeronodeman.h" +#include "script/sign.h" +#include "zeronode/swifttx.h" +#include "ui_interface.h" +#include "util.h" +#include "key_io.h" +#include "consensus/validation.h" +#include +#include +#include +#include + +#include +#include +#include + +using namespace std; +using namespace boost; + +// A helper object for signing messages from Zeronodes +CObfuScationSigner obfuScationSigner; +// Keep track of the active Zeronode +CActiveZeronode activeZeronode; + +bool GetTestingCollateralScript(std::string strAddress, CScript& script) +{ + if (!IsValidDestinationString(strAddress)) { + LogPrintf("GetTestingCollateralScript - Invalid collateral address\n"); + return false; + } + + auto dest = DecodeDestination(strAddress); + script = GetScriptForDestination(dest); + return true; +} + +bool CObfuScationSigner::IsVinAssociatedWithPubkey(CTxIn& vin, CPubKey& pubkey) +{ + CScript payee2; + payee2 = GetScriptForDestination(pubkey.GetID()); + + CTransaction txVin; + uint256 hash; + if (GetTransaction(vin.prevout.hash, txVin, hash, true)) { + BOOST_FOREACH (CTxOut out, txVin.vout) { + if (out.nValue == 10000 * COIN) { + if (out.scriptPubKey == payee2) return true; + } + } + } + + return false; +} + +bool CObfuScationSigner::SetKey(std::string strSecret, std::string& errorMessage, CKey& key, CPubKey& pubkey) +{ + CKey key2 = DecodeSecret(strSecret); + + if (!key2.IsValid()) { + errorMessage = _("Invalid private key."); + return false; + } + + key = key2; + pubkey = key.GetPubKey(); + + return true; +} + +bool CObfuScationSigner::GetKeysFromSecret(std::string strSecret, CKey& keyRet, CPubKey& pubkeyRet) +{ + CKey key2 = DecodeSecret(strSecret); + + if (!key2.IsValid()) return false; + + keyRet = key2; + pubkeyRet = keyRet.GetPubKey(); + + return true; +} + +bool CObfuScationSigner::SignMessage(std::string strMessage, std::string& errorMessage, vector& vchSig, CKey key) +{ + CHashWriter ss(SER_GETHASH, 0); + ss << strMessageMagic; + ss << strMessage; + + if (!key.SignCompact(ss.GetHash(), vchSig)) { + errorMessage = _("Signing failed."); + return false; + } + + return true; +} + +bool CObfuScationSigner::VerifyMessage(CPubKey pubkey, vector& vchSig, std::string strMessage, std::string& errorMessage) +{ + CHashWriter ss(SER_GETHASH, 0); + ss << strMessageMagic; + ss << strMessage; + + CPubKey pubkey2; + if (!pubkey2.RecoverCompact(ss.GetHash(), vchSig)) { + errorMessage = _("Error recovering public key."); + return false; + } + + if (fDebug && pubkey2.GetID() != pubkey.GetID()) + LogPrintf("CObfuScationSigner::VerifyMessage -- keys don't match: %s %s\n", pubkey2.GetID().ToString(), pubkey.GetID().ToString()); + + return (pubkey2.GetID() == pubkey.GetID()); +} + +//TODO: Rename/move to core +void ThreadCheckObfuScationPool() +{ + if (fLiteMode) return; //disable all Zeronode related functionality + + // Make this thread recognisable as the wallet flushing thread + RenameThread("zero-obfuscation"); + + unsigned int c = 0; + + while (true) { + MilliSleep(1000); + + // try to sync from all available nodes, one step at a time + zeronodeSync.Process(); + + if (zeronodeSync.IsBlockchainSynced()) { + c++; + + // check if we should activate or ping every few minutes, + // start right after sync is considered to be done + if (c % ZERONODE_PING_SECONDS == 1) activeZeronode.ManageStatus(); + + if (c % 60 == 0) { + znodeman.CheckAndRemove(); + zeronodePayments.CleanPaymentList(); + CleanTransactionLocksList(); + } + + if(c % ZERONODES_DUMP_SECONDS == 0) DumpZeronodes(); + + } + } +} diff --git a/src/zeronode/obfuscation.h b/src/zeronode/obfuscation.h new file mode 100644 index 00000000000..32837279886 --- /dev/null +++ b/src/zeronode/obfuscation.h @@ -0,0 +1,52 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef OBFUSCATION_H +#define OBFUSCATION_H + +#include "main.h" +#include "zeronode/activezeronode.h" +#include "zeronode/payments.h" +#include "zeronode/zeronode-sync.h" +#include "zeronode/zeronodeman.h" +#include "sync.h" + +class CTxIn; +class CObfuScationSigner; +class CZeroNodeVote; +class CActiveZeronode; + +// status update message constants +#define ZERONODE_ACCEPTED 1 +#define ZERONODE_REJECTED 0 +#define ZERONODE_RESET -1 + +extern CObfuScationSigner obfuScationSigner; +extern std::string strZeroNodePrivKey; +extern CActiveZeronode activeZeronode; + +bool GetTestingCollateralScript(std::string strAddress, CScript& script); + +/** Helper object for signing and checking signatures + */ +class CObfuScationSigner +{ +public: + /// Is the inputs associated with this public key? (and there is 10000 ZER - checking if valid zeronode) + bool IsVinAssociatedWithPubkey(CTxIn& vin, CPubKey& pubkey); + /// Set the private/public key values, returns true if successful + bool GetKeysFromSecret(std::string strSecret, CKey& keyRet, CPubKey& pubkeyRet); + /// Set the private/public key values, returns true if successful + bool SetKey(std::string strSecret, std::string& errorMessage, CKey& key, CPubKey& pubkey); + /// Sign the message, returns true if successful + bool SignMessage(std::string strMessage, std::string& errorMessage, std::vector& vchSig, CKey key); + /// Verify the message, returns true if succcessful + bool VerifyMessage(CPubKey pubkey, std::vector& vchSig, std::string strMessage, std::string& errorMessage); +}; + +void ThreadCheckObfuScationPool(); + +#endif diff --git a/src/zeronode/payments.cpp b/src/zeronode/payments.cpp new file mode 100644 index 00000000000..1cdd156cf03 --- /dev/null +++ b/src/zeronode/payments.cpp @@ -0,0 +1,856 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zeronode/payments.h" +#include "addrman.h" +#include "zeronode/budget.h" +#include "zeronode/zeronode-sync.h" +#include "zeronode/zeronodeman.h" +#include "zeronode/obfuscation.h" +#include "zeronode/spork.h" +#include "sync.h" +#include "util.h" +#include "utilmoneystr.h" +#include +#include + +#include "key_io.h" + +/** Object for who's going to get paid on which blocks */ +CZeronodePayments zeronodePayments; + +CCriticalSection cs_vecPayments; +CCriticalSection cs_mapZeronodeBlocks; +CCriticalSection cs_mapZeronodePayeeVotes; + +// +// CZeronodePaymentDB +// + +CZeronodePaymentDB::CZeronodePaymentDB() +{ + pathDB = GetDataDir() / "znpayments.dat"; + strMagicMessage = "ZeronodePayments"; +} + +bool CZeronodePaymentDB::Write(const CZeronodePayments& objToSave) +{ + int64_t nStart = GetTimeMillis(); + + // serialize, checksum data up to that point, then append checksum + CDataStream ssObj(SER_DISK, CLIENT_VERSION); + ssObj << strMagicMessage; // zeronode cache file specific magic message + ssObj << FLATDATA(Params().MessageStart()); // network specific magic number + ssObj << objToSave; + uint256 hash = Hash(ssObj.begin(), ssObj.end()); + ssObj << hash; + + // open output file, and associate with CAutoFile + FILE* file = fopen(pathDB.string().c_str(), "wb"); + CAutoFile fileout(file, SER_DISK, CLIENT_VERSION); + if (fileout.IsNull()) + return error("%s : Failed to open file %s", __func__, pathDB.string()); + + // Write and commit header, data + try { + fileout << ssObj; + } catch (std::exception& e) { + return error("%s : Serialize or I/O error - %s", __func__, e.what()); + } + fileout.fclose(); + + LogPrint("zeronode","Written info to znpayments.dat %dms\n", GetTimeMillis() - nStart); + + return true; +} + +CZeronodePaymentDB::ReadResult CZeronodePaymentDB::Read(CZeronodePayments& objToLoad, bool fDryRun) +{ + int64_t nStart = GetTimeMillis(); + // open input file, and associate with CAutoFile + FILE* file = fopen(pathDB.string().c_str(), "rb"); + CAutoFile filein(file, SER_DISK, CLIENT_VERSION); + if (filein.IsNull()) { + error("%s : Failed to open file %s", __func__, pathDB.string()); + return FileError; + } + + // use file size to size memory buffer + int fileSize = boost::filesystem::file_size(pathDB); + int dataSize = fileSize - sizeof(uint256); + // Don't try to resize to a negative number if file is small + if (dataSize < 0) + dataSize = 0; + vector vchData; + vchData.resize(dataSize); + uint256 hashIn; + + // read data and checksum from file + try { + filein.read((char*)&vchData[0], dataSize); + filein >> hashIn; + } catch (std::exception& e) { + error("%s : Deserialize or I/O error - %s", __func__, e.what()); + return HashReadError; + } + filein.fclose(); + + CDataStream ssObj(vchData, SER_DISK, CLIENT_VERSION); + + // verify stored checksum matches input data + uint256 hashTmp = Hash(ssObj.begin(), ssObj.end()); + if (hashIn != hashTmp) { + error("%s : Checksum mismatch, data corrupted", __func__); + return IncorrectHash; + } + + unsigned char pchMsgTmp[4]; + std::string strMagicMessageTmp; + try { + // de-serialize file header (zeronode cache file specific magic message) and .. + ssObj >> strMagicMessageTmp; + + // ... verify the message matches predefined one + if (strMagicMessage != strMagicMessageTmp) { + error("%s : Invalid zeronode payement cache magic message", __func__); + return IncorrectMagicMessage; + } + + + // de-serialize file header (network specific magic number) and .. + ssObj >> FLATDATA(pchMsgTmp); + + // ... verify the network matches ours + if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp))) { + error("%s : Invalid network magic number", __func__); + return IncorrectMagicNumber; + } + + // de-serialize data into CZeronodePayments object + ssObj >> objToLoad; + } catch (std::exception& e) { + objToLoad.Clear(); + error("%s : Deserialize or I/O error - %s", __func__, e.what()); + return IncorrectFormat; + } + + LogPrint("zeronode","Loaded info from znpayments.dat %dms\n", GetTimeMillis() - nStart); + LogPrint("zeronode"," %s\n", objToLoad.ToString()); + if (!fDryRun) { + LogPrint("zeronode","Zeronode payments manager - cleaning....\n"); + objToLoad.CleanPaymentList(); + LogPrint("zeronode","Zeronode payments manager - result:\n"); + LogPrint("zeronode"," %s\n", objToLoad.ToString()); + } + + return Ok; +} + +void DumpZeronodePayments() +{ + int64_t nStart = GetTimeMillis(); + + CZeronodePaymentDB paymentdb; + CZeronodePayments tempPayments; + + LogPrint("zeronode","Verifying znpayments.dat format...\n"); + CZeronodePaymentDB::ReadResult readResult = paymentdb.Read(tempPayments, true); + // there was an error and it was not an error on file opening => do not proceed + if (readResult == CZeronodePaymentDB::FileError) + LogPrint("zeronode","Missing budgets file - znpayments.dat, will try to recreate\n"); + else if (readResult != CZeronodePaymentDB::Ok) { + LogPrint("zeronode","Error reading znpayments.dat: "); + if (readResult == CZeronodePaymentDB::IncorrectFormat) + LogPrint("zeronode","magic is ok but data has invalid format, will try to recreate\n"); + else { + LogPrint("zeronode","file format is unknown or invalid, please fix it manually\n"); + return; + } + } + LogPrint("zeronode","Writting info to znpayments.dat...\n"); + paymentdb.Write(zeronodePayments); + + LogPrint("zeronode","Budget dump finished %dms\n", GetTimeMillis() - nStart); +} + +bool IsBlockValueValid(const CBlock& block, CAmount nExpectedValue) +{ + CBlockIndex* pindexPrev = chainActive.Tip(); + if (pindexPrev == NULL) return true; + + int nHeight = 0; + if (pindexPrev->GetBlockHash() == block.hashPrevBlock) { + nHeight = pindexPrev->nHeight + 1; + } else { //out of order + BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); + if (mi != mapBlockIndex.end() && (*mi).second) + nHeight = (*mi).second->nHeight + 1; + } + + if (nHeight == 0) { + LogPrint("zeronode","IsBlockValueValid() : WARNING: Couldn't find previous block\n"); + } + + if (!zeronodeSync.IsSynced()) { //there is no budget data to use to check anything + //super blocks will always be on these blocks, max 100 per budgeting + if (nHeight % GetBudgetPaymentCycleBlocks() < 100) { + return true; + } else { + if(block.vtx[0].GetValueOut() > nExpectedValue) return false; + } + } else { // we're synced and have data so check the budget schedule + + //are these blocks even enabled + if (!IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) { + if(block.vtx[0].GetValueOut() > nExpectedValue) return false; + } + + if (budget.IsBudgetPaymentBlock(nHeight)) { + //the value of the block is evaluated in CheckBlock + return true; + } else { + if(block.vtx[0].GetValueOut() > nExpectedValue) return false; + } + } + + return true; +} + +bool IsBlockPayeeValid(const CBlock& block, int nBlockHeight) +{ + if (!zeronodeSync.IsSynced()) { //there is no budget data to use to check anything -- find the longest chain + LogPrint("zeronode", "Client not synced, skipping block payee checks\n"); + return true; + } + + const CTransaction& txNew = block.vtx[0]; + + //check if it's a budget block + if (IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) { + if (budget.IsBudgetPaymentBlock(nBlockHeight)) { + if (budget.IsTransactionValid(txNew, nBlockHeight)) + return true; + + LogPrint("zeronode","Invalid budget payment detected %s\n", txNew.ToString().c_str()); + if (IsSporkActive(SPORK_9_ZERONODE_BUDGET_ENFORCEMENT)) + return false; + + LogPrint("zeronode","Budget enforcement is disabled, accepting block\n"); + return true; + } + } + + //check for zeronode payee + if (zeronodePayments.IsTransactionValid(txNew, nBlockHeight)) + return true; + + LogPrint("zeronode","Invalid zn payment detected %s\n", txNew.ToString().c_str()); + + if (IsSporkActive(SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT)) + return false; + LogPrint("zeronode","Zeronode payment enforcement is disabled, accepting block\n"); + + return true; +} + + +void FillBlockPayee(CMutableTransaction& txNew, CAmount nFees, CTxOut& txFounders, CTxOut& txZeronodes) +{ + CBlockIndex* pindexPrev = chainActive.Tip(); + if (!pindexPrev) return; + + if (IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && budget.IsBudgetPaymentBlock(pindexPrev->nHeight + 1)) { + budget.FillBlockPayee(txNew, nFees, txFounders, txZeronodes); + } else { + zeronodePayments.FillBlockPayee(txNew, nFees, txFounders, txZeronodes); + } +} + +std::string GetRequiredPaymentsString(int nBlockHeight) +{ + if (IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && budget.IsBudgetPaymentBlock(nBlockHeight)) { + return budget.GetRequiredPaymentsString(nBlockHeight); + } else { + return zeronodePayments.GetRequiredPaymentsString(nBlockHeight); + } +} + +void CZeronodePayments::FillBlockPayee(CMutableTransaction& txNew, int64_t nFees, CTxOut& txFounders, CTxOut& txZeronodes) +{ + CBlockIndex* pindexPrev = chainActive.Tip(); + if (!pindexPrev) return; + + bool hasPayment = true; + int nHeight = pindexPrev->nHeight+1; + CScript payee; + + //spork + if(!zeronodePayments.GetBlockPayee(nHeight, payee)){ + //no zeronode detected + CZeronode* winningNode = znodeman.GetCurrentZeroNode(1); + if (winningNode) { + payee = GetScriptForDestination(winningNode->pubKeyCollateralAddress.GetID()); + } else { + LogPrint("zeronode","CreateNewBlock: Failed to detect zeronode to pay\n"); + hasPayment = false; + } + } + + CAmount blockValue = GetBlockSubsidy(nHeight, Params().GetConsensus()); + CAmount zeronodePayment = GetZeronodePayment(nHeight, blockValue); + CAmount minerValue = blockValue; + + // Founders reward + CAmount vFoundersReward = blockValue * 7.5 / 100; + + if(hasPayment){ + minerValue -= zeronodePayment; + } + + txNew.vout[0].nValue = minerValue + nFees; + + if ((nHeight >= Params().GetConsensus().nFeeStartBlockHeight) && (nHeight <= Params().GetConsensus().GetLastFoundersRewardBlockHeight())) { + // Take some reward away from us + txNew.vout[0].nValue -= vFoundersReward; + + // And give it to the founders + txFounders = CTxOut(vFoundersReward, Params().GetFoundersRewardScriptAtHeight(nHeight)); + txNew.vout.push_back(txFounders); + } + + //@TODO zeronode + if(hasPayment == true && zeronodePayment > 0) { + txZeronodes = CTxOut(zeronodePayment, payee); + txNew.vout.push_back(txZeronodes); + + CTxDestination address1; + ExtractDestination(payee, address1); + + LogPrint("zeronode","Zeronode payment to %s\n", EncodeDestination(address1)); + } + LogPrint("zeronode","Total miner to %s\n", FormatMoney(txNew.vout[0].nValue).c_str()); + LogPrint("zeronode","Total founder to %s\n", FormatMoney(txFounders.nValue).c_str()); + LogPrint("zeronode","Total zero node to %s\n", FormatMoney(txZeronodes.nValue).c_str()); + LogPrint("zeronode","Total Coinbase to %s\n", FormatMoney(txNew.vout[0].nValue+txFounders.nValue+txZeronodes.nValue).c_str()); +} + +int CZeronodePayments::GetMinZeronodePaymentsProto() +{ + return MIN_PEER_PROTO_VERSION_ENFORCEMENT; // Also allow old peers as long as they are allowed to run +} + +void CZeronodePayments::ProcessMessageZeronodePayments(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) +{ + if (!zeronodeSync.IsBlockchainSynced()) return; + + if (fLiteMode) return; //disable all Obfuscation/Zeronode related functionality + + + if (strCommand == "znget") { //Zeronode Payments Request Sync + if (fLiteMode) return; //disable all Obfuscation/Zeronode related functionality + + int nCountNeeded; + vRecv >> nCountNeeded; + + if (NetworkIdFromCommandLine() == CBaseChainParams::MAIN) { + if (pfrom->HasFulfilledRequest("znget")) { + LogPrint("zeronode","znget - peer already asked me for the list\n"); + Misbehaving(pfrom->GetId(), 20); + return; + } + } + + pfrom->FulfilledRequest("znget"); + zeronodePayments.Sync(pfrom, nCountNeeded); + LogPrint("znpayments", "znget - Sent Zeronode winners to peer %i\n", pfrom->GetId()); + } else if (strCommand == "znw") { //Zeronode Payments Declare Winner + //this is required in litemodef + CZeronodePaymentWinner winner; + vRecv >> winner; + + if (pfrom->nVersion < ActiveProtocol()) return; + + int nHeight; + { + TRY_LOCK(cs_main, locked); + if (!locked || chainActive.Tip() == NULL) return; + nHeight = chainActive.Tip()->nHeight; + } + + if (zeronodePayments.mapZeronodePayeeVotes.count(winner.GetHash())) { + LogPrint("znpayments", "znw - Already seen - %s bestHeight %d\n", winner.GetHash().ToString().c_str(), nHeight); + zeronodeSync.AddedZeronodeWinner(winner.GetHash()); + return; + } + + int nFirstBlock = nHeight - (znodeman.CountEnabled() * 1.25); + if (winner.nBlockHeight < nFirstBlock || winner.nBlockHeight > nHeight + 20) { + LogPrint("znpayments", "znw - winner out of range - FirstBlock %d Height %d bestHeight %d\n", nFirstBlock, winner.nBlockHeight, nHeight); + return; + } + + std::string strError = ""; + if (!winner.IsValid(pfrom, strError)) { + if(strError != "") LogPrint("zeronode","znw - invalid message - %s\n", strError); + return; + } + + if (!zeronodePayments.CanVote(winner.vinZeronode.prevout, winner.nBlockHeight)) { + LogPrint("zeronode","znw - zeronode already voted - %s\n", winner.vinZeronode.prevout.ToStringShort()); + return; + } + + if (!winner.SignatureValid()) { + LogPrint("zeronode","znw - invalid signature\n"); + if (zeronodeSync.IsSynced()) + { + Misbehaving(pfrom->GetId(), 20); + } + // it could just be a non-synced zeronode + znodeman.AskForZN(pfrom, winner.vinZeronode); + return; + } + + CTxDestination address1; + ExtractDestination(winner.payee, address1); + + // LogPrint("znpayments", "znw - winning vote - Addr %s Height %d bestHeight %d - %s\n", EncodeDestination(address1), winner.nBlockHeight, nHeight, winner.vinZeronode.prevout.ToStringShort()); + + if (zeronodePayments.AddWinningZeronode(winner)) { + winner.Relay(); + zeronodeSync.AddedZeronodeWinner(winner.GetHash()); + } + } +} + +bool CZeronodePaymentWinner::Sign(CKey& keyZeronode, CPubKey& pubKeyZeronode) +{ + std::string errorMessage; + std::string strZeroNodeSignMessage; + + std::string strMessage = vinZeronode.prevout.ToStringShort() + + boost::lexical_cast(nBlockHeight) + + EncodeDestination(payee); + + if (!obfuScationSigner.SignMessage(strMessage, errorMessage, vchSig, keyZeronode)) { + return false; + } + + if (!obfuScationSigner.VerifyMessage(pubKeyZeronode, vchSig, strMessage, errorMessage)) { + return false; + } + + return true; +} + +bool CZeronodePayments::GetBlockPayee(int nBlockHeight, CScript& payee) +{ + if (mapZeronodeBlocks.count(nBlockHeight)) { + return mapZeronodeBlocks[nBlockHeight].GetPayee(payee); + } + + return false; +} + +// Is this zeronode scheduled to get paid soon? +// -- Only look ahead up to 8 blocks to allow for propagation of the latest 2 winners +bool CZeronodePayments::IsScheduled(CZeronode& zn, int nNotBlockHeight) +{ + LOCK(cs_mapZeronodeBlocks); + + int nHeight; + { + TRY_LOCK(cs_main, locked); + if (!locked || chainActive.Tip() == NULL) return false; + nHeight = chainActive.Tip()->nHeight; + } + + CScript znpayee; + znpayee = GetScriptForDestination(zn.pubKeyCollateralAddress.GetID()); + + CScript payee; + for (int64_t h = nHeight; h <= nHeight + 8; h++) { + if (h == nNotBlockHeight) continue; + if (mapZeronodeBlocks.count(h)) { + if (mapZeronodeBlocks[h].GetPayee(payee)) { + if (znpayee == payee) { + return true; + } + } + } + } + + return false; +} + +bool CZeronodePayments::AddWinningZeronode(CZeronodePaymentWinner& winnerIn) +{ + uint256 blockHash = uint256(); + + if (!GetBlockHash(blockHash, winnerIn.nBlockHeight - 100)) { + return false; + } + + { + LOCK2(cs_mapZeronodePayeeVotes, cs_mapZeronodeBlocks); + + if (mapZeronodePayeeVotes.count(winnerIn.GetHash())) { + return false; + } + + mapZeronodePayeeVotes[winnerIn.GetHash()] = winnerIn; + + if (!mapZeronodeBlocks.count(winnerIn.nBlockHeight)) { + CZeronodeBlockPayees blockPayees(winnerIn.nBlockHeight); + mapZeronodeBlocks[winnerIn.nBlockHeight] = blockPayees; + } + } + + mapZeronodeBlocks[winnerIn.nBlockHeight].AddPayee(winnerIn.payee, 1); + + return true; +} + +bool CZeronodeBlockPayees::IsTransactionValid(const CTransaction& txNew) +{ + LOCK(cs_vecPayments); + + int nMaxSignatures = 0; + int nZeronode_Drift_Count = 0; + + std::string strPayeesPossible = ""; + + CAmount nReward = GetBlockSubsidy(nBlockHeight, Params().GetConsensus()); + + if (IsSporkActive(SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT)) { + // Get a stable number of zeronodes by ignoring newly activated (< 8000 sec old) zeronodes + nZeronode_Drift_Count = znodeman.stable_size() + Params().ZeronodeCountDrift(); + } + else { + //account for the fact that all peers do not see the same zeronode count. A allowance of being off our zeronode count is given + //we only need to look at an increased zeronode count because as count increases, the reward decreases. This code only checks + //for znPayment >= required, so it only makes sense to check the max node count allowed. + nZeronode_Drift_Count = znodeman.size() + Params().ZeronodeCountDrift(); + } + + CAmount requiredZeronodePayment = GetZeronodePayment(nBlockHeight, nReward, nZeronode_Drift_Count); + + //require at least 6 signatures + BOOST_FOREACH (CZeronodePayee& payee, vecPayments) + { + LogPrint("zeronode","Zeronode payment nVotes=%d nMaxSignatures=%d\n", payee.nVotes, nMaxSignatures); + if (payee.nVotes >= nMaxSignatures && payee.nVotes >= ZNPAYMENTS_SIGNATURES_REQUIRED) + nMaxSignatures = payee.nVotes; + } + + //if we don't have at least 6 signatures on a payee, approve whichever is the longest chain + if (nMaxSignatures < ZNPAYMENTS_SIGNATURES_REQUIRED) return true; + + BOOST_FOREACH (CZeronodePayee& payee, vecPayments) { + bool found = false; + BOOST_FOREACH (CTxOut out, txNew.vout) { + if (payee.scriptPubKey == out.scriptPubKey) { + LogPrint("zeronode","Zeronode payment Paid=%s Min=%s\n", FormatMoney(out.nValue).c_str(), FormatMoney(requiredZeronodePayment).c_str()); + if(out.nValue == requiredZeronodePayment) + found = true; + else + LogPrint("zeronode","Zeronode payment is out of drift range"); + } + } + + if (found) return true; + + + try { + CTxDestination address1; + ExtractDestination(payee.scriptPubKey, address1); + + if (strPayeesPossible == "") { + strPayeesPossible += EncodeDestination(address1); + } else { + strPayeesPossible += "," + EncodeDestination(address1); + } + } catch (...) { } + } + + LogPrint("zeronode","CZeronodePayments::IsTransactionValid - Missing required payment of %s to %s\n", FormatMoney(requiredZeronodePayment).c_str(), strPayeesPossible.c_str()); + return false; +} + +std::string CZeronodeBlockPayees::GetRequiredPaymentsString() +{ + LOCK(cs_vecPayments); + + std::string ret = "Unknown"; + + BOOST_FOREACH (CZeronodePayee& payee, vecPayments) { + CTxDestination address1; + ExtractDestination(payee.scriptPubKey, address1); + + if (ret != "Unknown") { + ret += ", " + EncodeDestination(address1) + ":" + boost::lexical_cast(payee.nVotes); + } else { + ret = EncodeDestination(address1) + ":" + boost::lexical_cast(payee.nVotes); + } + } + + return ret; +} + +std::string CZeronodePayments::GetRequiredPaymentsString(int nBlockHeight) +{ + LOCK(cs_mapZeronodeBlocks); + + if (mapZeronodeBlocks.count(nBlockHeight)) { + return mapZeronodeBlocks[nBlockHeight].GetRequiredPaymentsString(); + } + + return "Unknown"; +} + +bool CZeronodePayments::IsTransactionValid(const CTransaction& txNew, int nBlockHeight) +{ + LOCK(cs_mapZeronodeBlocks); + + LogPrint("zeronode", "mapZeronodeBlocks size = %d, nBlockHeight = %d", mapZeronodeBlocks.size(), nBlockHeight); + if (mapZeronodeBlocks.count(nBlockHeight)) { + LogPrint("zeronode", "mapZeronodeBlocks check transaction"); + return mapZeronodeBlocks[nBlockHeight].IsTransactionValid(txNew); + } + + return true; +} + +void CZeronodePayments::CleanPaymentList() +{ + LOCK2(cs_mapZeronodePayeeVotes, cs_mapZeronodeBlocks); + + int nHeight; + { + TRY_LOCK(cs_main, locked); + if (!locked || chainActive.Tip() == NULL) return; + nHeight = chainActive.Tip()->nHeight; + } + + //keep up to five cycles for historical sake + int nLimit = std::max(int(znodeman.size() * 1.25), 1000); + + std::map::iterator it = mapZeronodePayeeVotes.begin(); + while (it != mapZeronodePayeeVotes.end()) { + CZeronodePaymentWinner winner = (*it).second; + + if (nHeight - winner.nBlockHeight > nLimit) { + LogPrint("znpayments", "CZeronodePayments::CleanPaymentList - Removing old Zeronode payment - block %d\n", winner.nBlockHeight); + zeronodeSync.mapSeenSyncZNW.erase((*it).first); + mapZeronodePayeeVotes.erase(it++); + mapZeronodeBlocks.erase(winner.nBlockHeight); + } else { + ++it; + } + } +} + +bool CZeronodePaymentWinner::IsValid(CNode* pnode, std::string& strError) +{ + CZeronode* pzn = znodeman.Find(vinZeronode); + + if (!pzn) { + strError = strprintf("Unknown Zeronode %s", vinZeronode.prevout.hash.ToString()); + LogPrint("zeronode","CZeronodePaymentWinner::IsValid - %s\n", strError); + znodeman.AskForZN(pnode, vinZeronode); + return false; + } + + if (pzn->protocolVersion < ActiveProtocol()) { + strError = strprintf("Zeronode protocol too old %d - req %d", pzn->protocolVersion, ActiveProtocol()); + LogPrint("zeronode","CZeronodePaymentWinner::IsValid - %s\n", strError); + return false; + } + + int n = znodeman.GetZeronodeRank(vinZeronode, nBlockHeight - 100, ActiveProtocol()); + + if (n > ZNPAYMENTS_SIGNATURES_TOTAL) { + //It's common to have zeronodes mistakenly think they are in the top 10 + // We don't want to print all of these messages, or punish them unless they're way off + if (n > ZNPAYMENTS_SIGNATURES_TOTAL * 2) { + strError = strprintf("Zeronode not in the top %d (%d)", ZNPAYMENTS_SIGNATURES_TOTAL * 2, n); + LogPrint("zeronode","CZeronodePaymentWinner::IsValid - %s\n", strError); + if (zeronodeSync.IsSynced()) Misbehaving(pnode->GetId(), 20); + } + return false; + } + + return true; +} + +bool CZeronodePayments::ProcessBlock(int nBlockHeight) +{ + if (!fZeroNode) return false; + + //reference node - hybrid mode + + int n = znodeman.GetZeronodeRank(activeZeronode.vin, nBlockHeight - 100, ActiveProtocol()); + + if (n == -1) { + LogPrint("zeronode", "CZeronodePayments::ProcessBlock - Unknown Zeronode\n"); + return false; + } + + if (n > ZNPAYMENTS_SIGNATURES_TOTAL) { + LogPrint("zeronode", "CZeronodePayments::ProcessBlock - Zeronode not in the top %d (%d)\n", ZNPAYMENTS_SIGNATURES_TOTAL, n); + return false; + } + + if (nBlockHeight <= nLastBlockHeight) return false; + + CZeronodePaymentWinner newWinner(activeZeronode.vin); + + if (budget.IsBudgetPaymentBlock(nBlockHeight)) { + //is budget payment block -- handled by the budgeting software + } else { + LogPrint("zeronode","CZeronodePayments::ProcessBlock() Start nHeight %d - vin %s. \n", nBlockHeight, activeZeronode.vin.prevout.hash.ToString()); + + // pay to the oldest ZN that still had no payment but its input is old enough and it was active long enough + int nCount = 0; + CZeronode* pzn = znodeman.GetNextZeronodeInQueueForPayment(nBlockHeight, true, nCount); + + if (pzn != NULL) { + LogPrint("zeronode","CZeronodePayments::ProcessBlock() Found by FindOldestNotInVec \n"); + + newWinner.nBlockHeight = nBlockHeight; + + CScript payee = GetScriptForDestination(pzn->pubKeyCollateralAddress.GetID()); + newWinner.AddPayee(payee); + + CTxDestination address1; + ExtractDestination(payee, address1); + + LogPrint("zeronode","CZeronodePayments::ProcessBlock() Winner payee %s nHeight %d. \n", EncodeDestination(address1), newWinner.nBlockHeight); + } else { + LogPrint("zeronode","CZeronodePayments::ProcessBlock() Failed to find zeronode to pay\n"); + } + } + + std::string errorMessage; + CPubKey pubKeyZeronode; + CKey keyZeronode; + + if (!obfuScationSigner.SetKey(strZeroNodePrivKey, errorMessage, keyZeronode, pubKeyZeronode)) { + LogPrint("zeronode","CZeronodePayments::ProcessBlock() - Error upon calling SetKey: %s\n", errorMessage.c_str()); + return false; + } + + LogPrint("zeronode","CZeronodePayments::ProcessBlock() - Signing Winner\n"); + if (newWinner.Sign(keyZeronode, pubKeyZeronode)) { + LogPrint("zeronode","CZeronodePayments::ProcessBlock() - AddWinningZeronode\n"); + + if (AddWinningZeronode(newWinner)) { + newWinner.Relay(); + nLastBlockHeight = nBlockHeight; + return true; + } + } + + return false; +} + +void CZeronodePaymentWinner::Relay() +{ + CInv inv(MSG_ZERONODE_WINNER, GetHash()); + RelayInv(inv); +} + +bool CZeronodePaymentWinner::SignatureValid() +{ + CZeronode* pzn = znodeman.Find(vinZeronode); + + if (pzn != NULL) { + std::string strMessage = vinZeronode.prevout.ToStringShort() + + boost::lexical_cast(nBlockHeight) + + EncodeDestination(payee); + + std::string errorMessage = ""; + if (!obfuScationSigner.VerifyMessage(pzn->pubKeyZeronode, vchSig, strMessage, errorMessage)) { + return error("CZeronodePaymentWinner::SignatureValid() - Got bad Zeronode address signature %s\n", vinZeronode.prevout.hash.ToString()); + } + + return true; + } + + return false; +} + +void CZeronodePayments::Sync(CNode* node, int nCountNeeded) +{ + LOCK(cs_mapZeronodePayeeVotes); + + int nHeight; + { + TRY_LOCK(cs_main, locked); + if (!locked || chainActive.Tip() == NULL) return; + nHeight = chainActive.Tip()->nHeight; + } + + int nCount = (znodeman.CountEnabled() * 1.25); + if (nCountNeeded > nCount) nCountNeeded = nCount; + + int nInvCount = 0; + std::map::iterator it = mapZeronodePayeeVotes.begin(); + while (it != mapZeronodePayeeVotes.end()) { + CZeronodePaymentWinner winner = (*it).second; + if (winner.nBlockHeight >= nHeight - nCountNeeded && winner.nBlockHeight <= nHeight + 20) { + node->PushInventory(CInv(MSG_ZERONODE_WINNER, winner.GetHash())); + nInvCount++; + } + ++it; + } + node->PushMessage("ssc", ZERONODE_SYNC_ZNW, nInvCount); +} + +std::string CZeronodePayments::ToString() const +{ + std::ostringstream info; + + info << "Votes: " << (int)mapZeronodePayeeVotes.size() << ", Blocks: " << (int)mapZeronodeBlocks.size(); + + return info.str(); +} + + +int CZeronodePayments::GetOldestBlock() +{ + LOCK(cs_mapZeronodeBlocks); + + int nOldestBlock = std::numeric_limits::max(); + + std::map::iterator it = mapZeronodeBlocks.begin(); + while (it != mapZeronodeBlocks.end()) { + if ((*it).first < nOldestBlock) { + nOldestBlock = (*it).first; + } + it++; + } + + return nOldestBlock; +} + + +int CZeronodePayments::GetNewestBlock() +{ + LOCK(cs_mapZeronodeBlocks); + + int nNewestBlock = 0; + + std::map::iterator it = mapZeronodeBlocks.begin(); + while (it != mapZeronodeBlocks.end()) { + if ((*it).first > nNewestBlock) { + nNewestBlock = (*it).first; + } + it++; + } + + return nNewestBlock; +} diff --git a/src/zeronode/payments.h b/src/zeronode/payments.h new file mode 100644 index 00000000000..68b45e36f90 --- /dev/null +++ b/src/zeronode/payments.h @@ -0,0 +1,304 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef ZERONODE_PAYMENTS_H +#define ZERONODE_PAYMENTS_H + +#include "key.h" +#include "key_io.h" +#include "main.h" +#include "zeronode/zeronode.h" +#include + +using namespace std; + +extern CCriticalSection cs_vecPayments; +extern CCriticalSection cs_mapZeronodeBlocks; +extern CCriticalSection cs_mapZeronodePayeeVotes; + +class CZeronodePayments; +class CZeronodePaymentWinner; +class CZeronodeBlockPayees; + +extern CZeronodePayments zeronodePayments; + +#define ZNPAYMENTS_SIGNATURES_REQUIRED 6 +#define ZNPAYMENTS_SIGNATURES_TOTAL 10 + +void ProcessMessageZeronodePayments(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); +bool IsBlockPayeeValid(const CBlock& block, int nBlockHeight); +std::string GetRequiredPaymentsString(int nBlockHeight); +bool IsBlockValueValid(const CBlock& block, CAmount nExpectedValue); +void FillBlockPayee(CMutableTransaction& txNew, CAmount nFees, CTxOut& txFounders, CTxOut& txZeronodes); + +void DumpZeronodePayments(); + +/** Save Zeronode Payment Data (znpayments.dat) + */ +class CZeronodePaymentDB +{ +private: + boost::filesystem::path pathDB; + std::string strMagicMessage; + +public: + enum ReadResult { + Ok, + FileError, + HashReadError, + IncorrectHash, + IncorrectMagicMessage, + IncorrectMagicNumber, + IncorrectFormat + }; + + CZeronodePaymentDB(); + bool Write(const CZeronodePayments& objToSave); + ReadResult Read(CZeronodePayments& objToLoad, bool fDryRun = false); +}; + +class CZeronodePayee +{ +public: + CScript scriptPubKey; + int nVotes; + + CZeronodePayee() + { + scriptPubKey = CScript(); + nVotes = 0; + } + + CZeronodePayee(CScript payee, int nVotesIn) + { + scriptPubKey = payee; + nVotes = nVotesIn; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(*(CScriptBase*)(&scriptPubKey)); + READWRITE(nVotes); + } +}; + +// Keep track of votes for payees from zeronodes +class CZeronodeBlockPayees +{ +public: + int nBlockHeight; + std::vector vecPayments; + + CZeronodeBlockPayees() + { + nBlockHeight = 0; + vecPayments.clear(); + } + CZeronodeBlockPayees(int nBlockHeightIn) + { + nBlockHeight = nBlockHeightIn; + vecPayments.clear(); + } + + void AddPayee(CScript payeeIn, int nIncrement) + { + LOCK(cs_vecPayments); + + BOOST_FOREACH (CZeronodePayee& payee, vecPayments) { + if (payee.scriptPubKey == payeeIn) { + payee.nVotes += nIncrement; + return; + } + } + + CZeronodePayee c(payeeIn, nIncrement); + vecPayments.push_back(c); + } + + bool GetPayee(CScript& payee) + { + LOCK(cs_vecPayments); + + int nVotes = -1; + BOOST_FOREACH (CZeronodePayee& p, vecPayments) { + if (p.nVotes > nVotes) { + payee = p.scriptPubKey; + nVotes = p.nVotes; + } + } + + return (nVotes > -1); + } + + bool HasPayeeWithVotes(CScript payee, int nVotesReq) + { + LOCK(cs_vecPayments); + + BOOST_FOREACH (CZeronodePayee& p, vecPayments) { + if (p.nVotes >= nVotesReq && p.scriptPubKey == payee) return true; + } + + return false; + } + + bool IsTransactionValid(const CTransaction& txNew); + std::string GetRequiredPaymentsString(); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(nBlockHeight); + READWRITE(vecPayments); + } +}; + +// for storing the winning payments +class CZeronodePaymentWinner +{ +public: + CTxIn vinZeronode; + + int nBlockHeight; + CScript payee; + std::vector vchSig; + + CZeronodePaymentWinner() + { + nBlockHeight = 0; + vinZeronode = CTxIn(); + payee = CScript(); + } + + CZeronodePaymentWinner(CTxIn vinIn) + { + nBlockHeight = 0; + vinZeronode = vinIn; + payee = CScript(); + } + + uint256 GetHash() + { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << *(CScriptBase*)(&payee); + ss << nBlockHeight; + ss << vinZeronode.prevout; + + return ss.GetHash(); + } + + bool Sign(CKey& keyZeronode, CPubKey& pubKeyZeronode); + bool IsValid(CNode* pnode, std::string& strError); + bool SignatureValid(); + void Relay(); + + void AddPayee(CScript payeeIn) + { + payee = payeeIn; + } + + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(vinZeronode); + READWRITE(nBlockHeight); + READWRITE(*(CScriptBase*)(&payee)); + READWRITE(vchSig); + } + + std::string ToString() + { + std::string ret = ""; + ret += vinZeronode.ToString(); + ret += ", " + boost::lexical_cast(nBlockHeight); + ret += ", " + EncodeDestination(payee); + ret += ", " + boost::lexical_cast((int)vchSig.size()); + return ret; + } +}; + +// +// Zeronode Payments Class +// Keeps track of who should get paid for which blocks +// + +class CZeronodePayments +{ +private: + int nSyncedFromPeer; + int nLastBlockHeight; + +public: + std::map mapZeronodePayeeVotes; + std::map mapZeronodeBlocks; + std::map mapZeronodesLastVote; //prevout.hash + prevout.n, nBlockHeight + + CZeronodePayments() + { + nSyncedFromPeer = 0; + nLastBlockHeight = 0; + } + + void Clear() + { + LOCK2(cs_mapZeronodeBlocks, cs_mapZeronodePayeeVotes); + mapZeronodeBlocks.clear(); + mapZeronodePayeeVotes.clear(); + } + + bool AddWinningZeronode(CZeronodePaymentWinner& winner); + bool ProcessBlock(int nBlockHeight); + + void Sync(CNode* node, int nCountNeeded); + void CleanPaymentList(); + int LastPayment(CZeronode& zn); + + bool GetBlockPayee(int nBlockHeight, CScript& payee); + bool IsTransactionValid(const CTransaction& txNew, int nBlockHeight); + bool IsScheduled(CZeronode& zn, int nNotBlockHeight); + + bool CanVote(COutPoint outZeronode, int nBlockHeight) + { + LOCK(cs_mapZeronodePayeeVotes); + uint256 temp = ArithToUint256(UintToArith256(outZeronode.hash) + outZeronode.n); + if(mapZeronodesLastVote.count(temp)) { + if(mapZeronodesLastVote[temp] == nBlockHeight) { + return false; + } + } + + //record this zeronode voted + mapZeronodesLastVote[temp] = nBlockHeight; + return true; + } + + int GetMinZeronodePaymentsProto(); + void ProcessMessageZeronodePayments(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); + std::string GetRequiredPaymentsString(int nBlockHeight); + void FillBlockPayee(CMutableTransaction& txNew, int64_t nFees, CTxOut& txFounders, CTxOut& txZeronodes); + std::string ToString() const; + int GetOldestBlock(); + int GetNewestBlock(); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(mapZeronodePayeeVotes); + READWRITE(mapZeronodeBlocks); + } +}; + + +#endif diff --git a/src/zeronode/spork.cpp b/src/zeronode/spork.cpp new file mode 100644 index 00000000000..18735aee935 --- /dev/null +++ b/src/zeronode/spork.cpp @@ -0,0 +1,278 @@ +// Copyright (c) 2014-2016 The Dash developers +// Copyright (c) 2016-2017 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zeronode/spork.h" +#include "base58.h" +#include "key.h" +#include "main.h" +#include "zeronode/budget.h" +#include "net.h" +#include "protocol.h" +#include "sync.h" +#include "zeronode/sporkdb.h" +#include "util.h" +#include "consensus/validation.h" +#include + +using namespace std; +using namespace boost; + +class CSporkMessage; +class CSporkManager; + +CSporkManager sporkManager; + +std::map mapSporks; +std::map mapSporksActive; + +// Zero: on startup load spork values from previous session if they exist in the sporkDB +void LoadSporksFromDB() +{ + for (int i = SPORK_START; i <= SPORK_END; ++i) { + // Since not all spork IDs are in use, we have to exclude undefined IDs + std::string strSpork = sporkManager.GetSporkNameByID(i); + if (strSpork == "Unknown") continue; + + // attempt to read spork from sporkDB + CSporkMessage spork; + if (!pSporkDB->ReadSpork(i, spork)) { + LogPrintf("%s : no previous value for %s found in database\n", __func__, strSpork); + continue; + } + + // add spork to memory + mapSporks[spork.GetHash()] = spork; + mapSporksActive[spork.nSporkID] = spork; + std::time_t result = spork.nValue; + // If SPORK Value is greater than 1,000,000 assume it's actually a Date and then convert to a more readable format + if (spork.nValue > 1000000) { + LogPrintf("%s : loaded spork %s with value %d : %s", __func__, + sporkManager.GetSporkNameByID(spork.nSporkID), spork.nValue, + std::ctime(&result)); + } else { + LogPrintf("%s : loaded spork %s with value %d\n", __func__, + sporkManager.GetSporkNameByID(spork.nSporkID), spork.nValue); + } + } +} + +void ProcessSpork(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) +{ + if (fLiteMode) return; //disable all obfuscation/zeronode related functionality + + if (strCommand == "spork") { + //LogPrintf("ProcessSpork::spork\n"); + CDataStream vMsg(vRecv); + CSporkMessage spork; + vRecv >> spork; + + if (chainActive.Tip() == NULL) return; + + // Ignore spork messages about unknown/deleted sporks + std::string strSpork = sporkManager.GetSporkNameByID(spork.nSporkID); + if (strSpork == "Unknown") return; + + uint256 hash = spork.GetHash(); + if (mapSporksActive.count(spork.nSporkID)) { + if (mapSporksActive[spork.nSporkID].nTimeSigned >= spork.nTimeSigned) { + if (fDebug) LogPrintf("spork - seen %s block %d \n", hash.ToString(), chainActive.Tip()->nHeight); + return; + } else { + if (fDebug) LogPrintf("spork - got updated spork %s block %d \n", hash.ToString(), chainActive.Tip()->nHeight); + } + } + + LogPrintf("spork - new %s ID %d Time %d bestHeight %d\n", hash.ToString(), spork.nSporkID, spork.nValue, chainActive.Tip()->nHeight); + + if (!sporkManager.CheckSignature(spork)) { + LogPrintf("spork - invalid signature\n"); + Misbehaving(pfrom->GetId(), 100); + return; + } + + mapSporks[hash] = spork; + mapSporksActive[spork.nSporkID] = spork; + sporkManager.Relay(spork); + + // Zero: add to spork database. + pSporkDB->WriteSpork(spork.nSporkID, spork); + } + if (strCommand == "getsporks") { + std::map::iterator it = mapSporksActive.begin(); + + while (it != mapSporksActive.end()) { + pfrom->PushMessage("spork", it->second); + it++; + } + } +} + + +// grab the value of the spork on the network, or the default +int64_t GetSporkValue(int nSporkID) +{ + int64_t r = -1; + + if (mapSporksActive.count(nSporkID)) { + r = mapSporksActive[nSporkID].nValue; + } else { + if (nSporkID == SPORK_2_SWIFTTX) r = SPORK_2_SWIFTTX_DEFAULT; + if (nSporkID == SPORK_3_SWIFTTX_BLOCK_FILTERING) r = SPORK_3_SWIFTTX_BLOCK_FILTERING_DEFAULT; + if (nSporkID == SPORK_6_ZERONODE_FULL_PAYMENT_ENABLED) r = SPORK_6_ZERONODE_FULL_PAYMENT_ENABLED_DEFAULT; + if (nSporkID == SPORK_7_ZERONODE_PAYMENT_ENABLED) r = SPORK_7_ZERONODE_PAYMENT_ENABLED_DEFAULT; + if (nSporkID == SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT) r = SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT_DEFAULT; + if (nSporkID == SPORK_9_ZERONODE_BUDGET_ENFORCEMENT) r = SPORK_9_ZERONODE_BUDGET_ENFORCEMENT_DEFAULT; + if (nSporkID == SPORK_13_ENABLE_SUPERBLOCKS) r = SPORK_13_ENABLE_SUPERBLOCKS_DEFAULT; + + if (r == -1) LogPrintf("GetSpork::Unknown Spork %d\n", nSporkID); + } + + return r; +} + +// grab the spork value, and see if it's off +bool IsSporkActive(int nSporkID) +{ + int64_t r = GetSporkValue(nSporkID); + if (r == -1) return false; + return r < GetTime(); +} + + +void ReprocessBlocks(int nBlocks) +{ + std::map::iterator it = mapRejectedBlocks.begin(); + while (it != mapRejectedBlocks.end()) { + //use a window twice as large as is usual for the nBlocks we want to reset + if ((*it).second > GetTime() - (nBlocks * 60 * 5)) { + BlockMap::iterator mi = mapBlockIndex.find((*it).first); + if (mi != mapBlockIndex.end() && (*mi).second) { + LOCK(cs_main); + + CBlockIndex* pindex = (*mi).second; + LogPrintf("ReprocessBlocks - %s\n", (*it).first.ToString()); + + CValidationState state; + ReconsiderBlock(state, pindex); + } + } + ++it; + } + + CValidationState state; + { + LOCK(cs_main); + DisconnectBlocksAndReprocess(nBlocks); + } + + if (state.IsValid()) { + ActivateBestChain(state); + } +} + +bool CSporkManager::CheckSignature(CSporkMessage& spork) +{ + //note: need to investigate why this is failing + std::string strMessage = boost::lexical_cast(spork.nSporkID) + boost::lexical_cast(spork.nValue) + boost::lexical_cast(spork.nTimeSigned); + CPubKey pubkeynew(ParseHex(Params().SporkKey())); + std::string errorMessage = ""; + if (obfuScationSigner.VerifyMessage(pubkeynew, spork.vchSig, strMessage, errorMessage)) { + return true; + } + + return false; +} + +bool CSporkManager::Sign(CSporkMessage& spork) +{ + std::string strMessage = boost::lexical_cast(spork.nSporkID) + boost::lexical_cast(spork.nValue) + boost::lexical_cast(spork.nTimeSigned); + + CKey key2; + CPubKey pubkey2; + std::string errorMessage = ""; + + if (!obfuScationSigner.SetKey(strMasterPrivKey, errorMessage, key2, pubkey2)) { + LogPrintf("CZeronodePayments::Sign - ERROR: Invalid zeronodeprivkey: '%s'\n", errorMessage); + return false; + } + + if (!obfuScationSigner.SignMessage(strMessage, errorMessage, spork.vchSig, key2)) { + LogPrintf("CZeronodePayments::Sign - Sign message failed"); + return false; + } + + if (!obfuScationSigner.VerifyMessage(pubkey2, spork.vchSig, strMessage, errorMessage)) { + LogPrintf("CZeronodePayments::Sign - Verify message failed"); + return false; + } + + return true; +} + +bool CSporkManager::UpdateSpork(int nSporkID, int64_t nValue) +{ + CSporkMessage msg; + msg.nSporkID = nSporkID; + msg.nValue = nValue; + msg.nTimeSigned = GetTime(); + + if (Sign(msg)) { + Relay(msg); + mapSporks[msg.GetHash()] = msg; + mapSporksActive[nSporkID] = msg; + return true; + } + + return false; +} + +void CSporkManager::Relay(CSporkMessage& msg) +{ + CInv inv(MSG_SPORK, msg.GetHash()); + RelayInv(inv); +} + +bool CSporkManager::SetPrivKey(std::string strPrivKey) +{ + CSporkMessage msg; + + // Test signing successful, proceed + strMasterPrivKey = strPrivKey; + + Sign(msg); + + if (CheckSignature(msg)) { + LogPrintf("CSporkManager::SetPrivKey - Successfully initialized as spork signer\n"); + return true; + } else { + return false; + } +} + +int CSporkManager::GetSporkIDByName(std::string strName) +{ + if (strName == "SPORK_2_SWIFTTX") return SPORK_2_SWIFTTX; + if (strName == "SPORK_3_SWIFTTX_BLOCK_FILTERING") return SPORK_3_SWIFTTX_BLOCK_FILTERING; + if (strName == "SPORK_6_ZERONODE_FULL_PAYMENT_ENABLED") return SPORK_6_ZERONODE_FULL_PAYMENT_ENABLED; + if (strName == "SPORK_7_ZERONODE_PAYMENT_ENABLED") return SPORK_7_ZERONODE_PAYMENT_ENABLED; + if (strName == "SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT") return SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT; + if (strName == "SPORK_9_ZERONODE_BUDGET_ENFORCEMENT") return SPORK_9_ZERONODE_BUDGET_ENFORCEMENT; + if (strName == "SPORK_13_ENABLE_SUPERBLOCKS") return SPORK_13_ENABLE_SUPERBLOCKS; + + return -1; +} + +std::string CSporkManager::GetSporkNameByID(int id) +{ + if (id == SPORK_2_SWIFTTX) return "SPORK_2_SWIFTTX"; + if (id == SPORK_3_SWIFTTX_BLOCK_FILTERING) return "SPORK_3_SWIFTTX_BLOCK_FILTERING"; + if (id == SPORK_6_ZERONODE_FULL_PAYMENT_ENABLED) return "SPORK_6_ZERONODE_FULL_PAYMENT_ENABLED"; + if (id == SPORK_7_ZERONODE_PAYMENT_ENABLED) return "SPORK_7_ZERONODE_PAYMENT_ENABLED"; + if (id == SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT) return "SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT"; + if (id == SPORK_9_ZERONODE_BUDGET_ENFORCEMENT) return "SPORK_9_ZERONODE_BUDGET_ENFORCEMENT"; + if (id == SPORK_13_ENABLE_SUPERBLOCKS) return "SPORK_13_ENABLE_SUPERBLOCKS"; + + return "Unknown"; +} diff --git a/src/zeronode/spork.h b/src/zeronode/spork.h new file mode 100644 index 00000000000..6ec6c887e71 --- /dev/null +++ b/src/zeronode/spork.h @@ -0,0 +1,116 @@ +// Copyright (c) 2014-2016 The Dash developers +// Copyright (c) 2016-2017 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef SPORK_H +#define SPORK_H + +#include "base58.h" +#include "key.h" +#include "main.h" +#include "net.h" +#include "sync.h" +#include "util.h" + +#include "zeronode/obfuscation.h" +#include "protocol.h" +#include + +using namespace std; +using namespace boost; + +/* + Don't ever reuse these IDs for other sporks + - This would result in old clients getting confused about which spork is for what + + Sporks 11,12, and 16 to be removed with 1st zerocoin release +*/ +#define SPORK_START 10001 +#define SPORK_END 10015 + +#define SPORK_2_SWIFTTX 10001 +#define SPORK_3_SWIFTTX_BLOCK_FILTERING 10002 +#define SPORK_6_ZERONODE_FULL_PAYMENT_ENABLED 10005 +#define SPORK_7_ZERONODE_PAYMENT_ENABLED 10006 +#define SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT 10007 +#define SPORK_9_ZERONODE_BUDGET_ENFORCEMENT 10008 +#define SPORK_13_ENABLE_SUPERBLOCKS 10012 + +#define SPORK_2_SWIFTTX_DEFAULT 4070908800 //OFF +#define SPORK_3_SWIFTTX_BLOCK_FILTERING_DEFAULT 4070908800 //OFF +#define SPORK_6_ZERONODE_FULL_PAYMENT_ENABLED_DEFAULT 4070908800 //OFF +#define SPORK_7_ZERONODE_PAYMENT_ENABLED_DEFAULT 4070908800 //OFF +#define SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT_DEFAULT 4070908800 //OFF +#define SPORK_9_ZERONODE_BUDGET_ENFORCEMENT_DEFAULT 4070908800 //OFF +#define SPORK_13_ENABLE_SUPERBLOCKS_DEFAULT 4070908800 //OFF + +class CSporkMessage; +class CSporkManager; + +extern std::map mapSporks; +extern std::map mapSporksActive; +extern CSporkManager sporkManager; + +void LoadSporksFromDB(); +void ProcessSpork(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); +int64_t GetSporkValue(int nSporkID); +bool IsSporkActive(int nSporkID); +void ReprocessBlocks(int nBlocks); + +// +// Spork Class +// Keeps track of all of the network spork settings +// + +class CSporkMessage +{ +public: + std::vector vchSig; + int nSporkID; + int64_t nValue; + int64_t nTimeSigned; + + uint256 GetHash() const + { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << nSporkID; + ss << nValue; + ss << nTimeSigned; + return ss.GetHash(); + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(nSporkID); + READWRITE(nValue); + READWRITE(nTimeSigned); + READWRITE(vchSig); + } +}; + + +class CSporkManager +{ +private: + std::vector vchSig; + std::string strMasterPrivKey; + +public: + CSporkManager() + { + } + + std::string GetSporkNameByID(int id); + int GetSporkIDByName(std::string strName); + bool UpdateSpork(int nSporkID, int64_t nValue); + bool SetPrivKey(std::string strPrivKey); + bool CheckSignature(CSporkMessage& spork); + bool Sign(CSporkMessage& spork); + void Relay(CSporkMessage& msg); +}; + +#endif diff --git a/src/zeronode/sporkdb.cpp b/src/zeronode/sporkdb.cpp new file mode 100644 index 00000000000..d39e6bc2a16 --- /dev/null +++ b/src/zeronode/sporkdb.cpp @@ -0,0 +1,25 @@ +// Copyright (c) 2017 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zeronode/sporkdb.h" +#include "zeronode/spork.h" + +CSporkDB::CSporkDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "sporks", nCacheSize, fMemory, fWipe) {} + +bool CSporkDB::WriteSpork(const int nSporkId, const CSporkMessage& spork) +{ + LogPrintf("Wrote spork %s to database\n", sporkManager.GetSporkNameByID(nSporkId)); + return Write(nSporkId, spork); + +} + +bool CSporkDB::ReadSpork(const int nSporkId, CSporkMessage& spork) +{ + return Read(nSporkId, spork); +} + +bool CSporkDB::SporkExists(const int nSporkId) +{ + return Exists(nSporkId); +} diff --git a/src/zeronode/sporkdb.h b/src/zeronode/sporkdb.h new file mode 100644 index 00000000000..8af01b8c7ec --- /dev/null +++ b/src/zeronode/sporkdb.h @@ -0,0 +1,28 @@ +// Copyright (c) 2017 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef Solaris_CSPORKDB_H +#define Solaris_CSPORKDB_H + +#include +#include "dbwrapper.h" +#include "zeronode/spork.h" + +class CSporkDB : public CDBWrapper +{ +public: + CSporkDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); + +private: + CSporkDB(const CSporkDB&); + void operator=(const CSporkDB&); + +public: + bool WriteSpork(const int nSporkId, const CSporkMessage& spork); + bool ReadSpork(const int nSporkId, CSporkMessage& spork); + bool SporkExists(const int nSporkId); +}; + + +#endif //Solaris_CSPORKDB_H diff --git a/src/zeronode/swifttx.cpp b/src/zeronode/swifttx.cpp new file mode 100644 index 00000000000..f25a4e6ef28 --- /dev/null +++ b/src/zeronode/swifttx.cpp @@ -0,0 +1,555 @@ +// Copyright (c) 2014-2016 The Dash developers +// Copyright (c) 2016-2017 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zeronode/swifttx.h" +#include "zeronode/activezeronode.h" +#include "base58.h" +#include "key.h" +#include "zeronode/zeronodeman.h" +#include "net.h" +#include "zeronode/obfuscation.h" +#include "protocol.h" +#include "zeronode/spork.h" +#include "sync.h" +#include "util.h" +#include "consensus/validation.h" +#include + +using namespace std; +using namespace boost; + +std::map mapTxLockReq; +std::map mapTxLockReqRejected; +std::map mapTxLockVote; +std::map mapTxLocks; +std::map mapLockedInputs; +std::map mapUnknownVotes; //track votes with no tx for DOS +int nCompleteTXLocks; + +//txlock - Locks transaction +// +//step 1.) Broadcast intention to lock transaction inputs, "txlreg", CTransaction +//step 2.) Top SWIFTTX_SIGNATURES_TOTAL zeronodes, open connect to top 1 zeronode. +// Send "txvote", CTransaction, Signature, Approve +//step 3.) Top 1 zeronode, waits for SWIFTTX_SIGNATURES_REQUIRED messages. Upon success, sends "txlock' + +void ProcessMessageSwiftTX(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) +{ + if (fLiteMode) return; //disable all obfuscation/zeronode related functionality + if (!IsSporkActive(SPORK_2_SWIFTTX)) return; + if (!zeronodeSync.IsBlockchainSynced()) return; + + if (strCommand == "ix") { + //LogPrintf("ProcessMessageSwiftTX::ix\n"); + CDataStream vMsg(vRecv); + CTransaction tx; + vRecv >> tx; + + CInv inv(MSG_TXLOCK_REQUEST, tx.GetHash()); + pfrom->AddInventoryKnown(inv); + + if (mapTxLockReq.count(tx.GetHash()) || mapTxLockReqRejected.count(tx.GetHash())) { + return; + } + + if (!IsIXTXValid(tx)) { + return; + } + + BOOST_FOREACH (const CTxOut o, tx.vout) { + // IX supports normal scripts and unspendable scripts (used in DS collateral and Budget collateral). + // TODO: Look into other script types that are normal and can be included + if (!o.scriptPubKey.IsNormalPaymentScript() && !o.scriptPubKey.IsUnspendable()) { + LogPrintf("ProcessMessageSwiftTX::ix - Invalid Script %s\n", tx.ToString().c_str()); + return; + } + } + + int nBlockHeight = CreateNewLock(tx); + + bool fMissingInputs = false; + CValidationState state; + + bool fAccepted = false; + { + LOCK(cs_main); + fAccepted = AcceptToMemoryPool(mempool, state, tx, true, &fMissingInputs); + } + if (fAccepted) { + RelayInv(inv); + + DoConsensusVote(tx, nBlockHeight); + + mapTxLockReq.insert(make_pair(tx.GetHash(), tx)); + + LogPrintf("ProcessMessageSwiftTX::ix - Transaction Lock Request: %s %s : accepted %s\n", + pfrom->addr.ToString().c_str(), pfrom->cleanSubVer.c_str(), + tx.GetHash().ToString().c_str()); + + return; + + } else { + mapTxLockReqRejected.insert(make_pair(tx.GetHash(), tx)); + + // can we get the conflicting transaction as proof? + + LogPrintf("ProcessMessageSwiftTX::ix - Transaction Lock Request: %s %s : rejected %s\n", + pfrom->addr.ToString().c_str(), pfrom->cleanSubVer.c_str(), + tx.GetHash().ToString().c_str()); + + BOOST_FOREACH (const CTxIn& in, tx.vin) { + if (!mapLockedInputs.count(in.prevout)) { + mapLockedInputs.insert(make_pair(in.prevout, tx.GetHash())); + } + } + + // resolve conflicts + std::map::iterator i = mapTxLocks.find(tx.GetHash()); + if (i != mapTxLocks.end()) { + //we only care if we have a complete tx lock + if ((*i).second.CountSignatures() >= SWIFTTX_SIGNATURES_REQUIRED) { + if (!CheckForConflictingLocks(tx)) { + LogPrintf("ProcessMessageSwiftTX::ix - Found Existing Complete IX Lock\n"); + + //reprocess the last 15 blocks + ReprocessBlocks(15); + mapTxLockReq.insert(make_pair(tx.GetHash(), tx)); + } + } + } + + return; + } + } else if (strCommand == "txlvote") // SwiftX Lock Consensus Votes + { + CConsensusVote ctx; + vRecv >> ctx; + + CInv inv(MSG_TXLOCK_VOTE, ctx.GetHash()); + pfrom->AddInventoryKnown(inv); + + if (mapTxLockVote.count(ctx.GetHash())) { + return; + } + + mapTxLockVote.insert(make_pair(ctx.GetHash(), ctx)); + + if (ProcessConsensusVote(pfrom, ctx)) { + //Spam/Dos protection + /* + Zeronodes will sometimes propagate votes before the transaction is known to the client. + This tracks those messages and allows it at the same rate of the rest of the network, if + a peer violates it, it will simply be ignored + */ + if (!mapTxLockReq.count(ctx.txHash) && !mapTxLockReqRejected.count(ctx.txHash)) { + if (!mapUnknownVotes.count(ctx.vinZeronode.prevout.hash)) { + mapUnknownVotes[ctx.vinZeronode.prevout.hash] = GetTime() + (60 * 10); + } + + if (mapUnknownVotes[ctx.vinZeronode.prevout.hash] > GetTime() && + mapUnknownVotes[ctx.vinZeronode.prevout.hash] - GetAverageVoteTime() > 60 * 10) { + LogPrintf("ProcessMessageSwiftTX::ix - zeronode is spamming transaction votes: %s %s\n", + ctx.vinZeronode.ToString().c_str(), + ctx.txHash.ToString().c_str()); + return; + } else { + mapUnknownVotes[ctx.vinZeronode.prevout.hash] = GetTime() + (60 * 10); + } + } + RelayInv(inv); + } + + return; + } +} + +bool IsIXTXValid(const CTransaction& txCollateral) +{ + if (txCollateral.vout.size() < 1) return false; + if (txCollateral.nLockTime != 0) return false; + + CAmount nValueIn = 0; + CAmount nValueOut = 0; + bool missingTx = false; + + BOOST_FOREACH (const CTxOut o, txCollateral.vout) + nValueOut += o.nValue; + + BOOST_FOREACH (const CTxIn i, txCollateral.vin) { + CTransaction tx2; + uint256 hash; + if (GetTransaction(i.prevout.hash, tx2, hash, true)) { + if (tx2.vout.size() > i.prevout.n) { + nValueIn += tx2.vout[i.prevout.n].nValue; + } + } else { + missingTx = true; + } + } + + // if (nValueOut > GetSporkValue(SPORK_5_MAX_VALUE) * COIN) { + // LogPrint("swiftx", "IsIXTXValid - Transaction value too high - %s\n", txCollateral.ToString().c_str()); + // return false; + // } + + if (missingTx) { + LogPrint("swiftx", "IsIXTXValid - Unknown inputs in IX transaction - %s\n", txCollateral.ToString().c_str()); + /* + This happens sometimes for an unknown reason, so we'll return that it's a valid transaction. + If someone submits an invalid transaction it will be rejected by the network anyway and this isn't + very common, but we don't want to block IX just because the client can't figure out the fee. + */ + return true; + } + + if (nValueIn - nValueOut < COIN * 0.0001) { + LogPrint("swiftx", "IsIXTXValid - did not include enough fees in transaction %d\n%s\n", nValueOut - nValueIn, txCollateral.ToString().c_str()); + return false; + } + + return true; +} + +int64_t CreateNewLock(CTransaction tx) +{ + int64_t nTxAge = 0; + BOOST_REVERSE_FOREACH (CTxIn i, tx.vin) { + nTxAge = GetInputAge(i); + if (nTxAge < 5) //1 less than the "send IX" gui requires, incase of a block propagating the network at the time + { + LogPrintf("CreateNewLock - Transaction not found / too new: %d / %s\n", nTxAge, tx.GetHash().ToString().c_str()); + return 0; + } + } + + /* + Use a blockheight newer than the input. + This prevents attackers from using transaction mallibility to predict which zeronodes + they'll use. + */ + int nBlockHeight = (chainActive.Tip()->nHeight - nTxAge) + 4; + + if (!mapTxLocks.count(tx.GetHash())) { + LogPrintf("CreateNewLock - New Transaction Lock %s !\n", tx.GetHash().ToString().c_str()); + + CTransactionLock newLock; + newLock.nBlockHeight = nBlockHeight; + newLock.nExpiration = GetTime() + (60 * 60); //locks expire after 60 minutes (24 confirmations) + newLock.nTimeout = GetTime() + (60 * 5); + newLock.txHash = tx.GetHash(); + mapTxLocks.insert(make_pair(tx.GetHash(), newLock)); + } else { + mapTxLocks[tx.GetHash()].nBlockHeight = nBlockHeight; + LogPrint("swiftx", "CreateNewLock - Transaction Lock Exists %s !\n", tx.GetHash().ToString().c_str()); + } + + + return nBlockHeight; +} + +// check if we need to vote on this transaction +void DoConsensusVote(CTransaction& tx, int64_t nBlockHeight) +{ + if (!fZeroNode) return; + + int n = znodeman.GetZeronodeRank(activeZeronode.vin, nBlockHeight, MIN_SWIFTTX_PROTO_VERSION); + + if (n == -1) { + LogPrint("swiftx", "SwiftX::DoConsensusVote - Unknown Zeronode\n"); + return; + } + + if (n > SWIFTTX_SIGNATURES_TOTAL) { + LogPrint("swiftx", "SwiftX::DoConsensusVote - Zeronode not in the top %d (%d)\n", SWIFTTX_SIGNATURES_TOTAL, n); + return; + } + /* + nBlockHeight calculated from the transaction is the authoritive source + */ + + LogPrint("swiftx", "SwiftX::DoConsensusVote - In the top %d (%d)\n", SWIFTTX_SIGNATURES_TOTAL, n); + + CConsensusVote ctx; + ctx.vinZeronode = activeZeronode.vin; + ctx.txHash = tx.GetHash(); + ctx.nBlockHeight = nBlockHeight; + if (!ctx.Sign()) { + LogPrintf("SwiftX::DoConsensusVote - Failed to sign consensus vote\n"); + return; + } + if (!ctx.SignatureValid()) { + LogPrintf("SwiftX::DoConsensusVote - Signature invalid\n"); + return; + } + + mapTxLockVote[ctx.GetHash()] = ctx; + + CInv inv(MSG_TXLOCK_VOTE, ctx.GetHash()); + RelayInv(inv); +} + +//received a consensus vote +bool ProcessConsensusVote(CNode* pnode, CConsensusVote& ctx) +{ + int n = znodeman.GetZeronodeRank(ctx.vinZeronode, ctx.nBlockHeight, MIN_SWIFTTX_PROTO_VERSION); + + CZeronode* pzn = znodeman.Find(ctx.vinZeronode); + if (pzn != NULL) + LogPrint("swiftx", "SwiftX::ProcessConsensusVote - Zeronode ADDR %s %d\n", pzn->addr.ToString().c_str(), n); + + if (n == -1) { + //can be caused by past versions trying to vote with an invalid protocol + LogPrint("swiftx", "SwiftX::ProcessConsensusVote - Unknown Zeronode\n"); + znodeman.AskForZN(pnode, ctx.vinZeronode); + return false; + } + + if (n > SWIFTTX_SIGNATURES_TOTAL) { + LogPrint("swiftx", "SwiftX::ProcessConsensusVote - Zeronode not in the top %d (%d) - %s\n", SWIFTTX_SIGNATURES_TOTAL, n, ctx.GetHash().ToString().c_str()); + return false; + } + + if (!ctx.SignatureValid()) { + LogPrintf("SwiftX::ProcessConsensusVote - Signature invalid\n"); + // don't ban, it could just be a non-synced zeronode + znodeman.AskForZN(pnode, ctx.vinZeronode); + return false; + } + + if (!mapTxLocks.count(ctx.txHash)) { + LogPrintf("SwiftX::ProcessConsensusVote - New Transaction Lock %s !\n", ctx.txHash.ToString().c_str()); + + CTransactionLock newLock; + newLock.nBlockHeight = 0; + newLock.nExpiration = GetTime() + (60 * 60); + newLock.nTimeout = GetTime() + (60 * 5); + newLock.txHash = ctx.txHash; + mapTxLocks.insert(make_pair(ctx.txHash, newLock)); + } else + LogPrint("swiftx", "SwiftX::ProcessConsensusVote - Transaction Lock Exists %s !\n", ctx.txHash.ToString().c_str()); + + //compile consessus vote + std::map::iterator i = mapTxLocks.find(ctx.txHash); + if (i != mapTxLocks.end()) { + (*i).second.AddSignature(ctx); + +#ifdef ENABLE_WALLET + if (pwalletMain) { + //when we get back signatures, we'll count them as requests. Otherwise the client will think it didn't propagate. + if (pwalletMain->mapRequestCount.count(ctx.txHash)) + pwalletMain->mapRequestCount[ctx.txHash]++; + } +#endif + + LogPrint("swiftx", "SwiftX::ProcessConsensusVote - Transaction Lock Votes %d - %s !\n", (*i).second.CountSignatures(), ctx.GetHash().ToString().c_str()); + + if ((*i).second.CountSignatures() >= SWIFTTX_SIGNATURES_REQUIRED) { + LogPrint("swiftx", "SwiftX::ProcessConsensusVote - Transaction Lock Is Complete %s !\n", (*i).second.GetHash().ToString().c_str()); + + CTransaction& tx = mapTxLockReq[ctx.txHash]; + if (!CheckForConflictingLocks(tx)) { +#ifdef ENABLE_WALLET + if (pwalletMain) { + if (pwalletMain->UpdatedTransaction((*i).second.txHash)) { + nCompleteTXLocks++; + } + } +#endif + + if (mapTxLockReq.count(ctx.txHash)) { + BOOST_FOREACH (const CTxIn& in, tx.vin) { + if (!mapLockedInputs.count(in.prevout)) { + mapLockedInputs.insert(make_pair(in.prevout, ctx.txHash)); + } + } + } + + // resolve conflicts + + //if this tx lock was rejected, we need to remove the conflicting blocks + if (mapTxLockReqRejected.count((*i).second.txHash)) { + //reprocess the last 15 blocks + ReprocessBlocks(15); + } + } + } + return true; + } + + + return false; +} + +bool CheckForConflictingLocks(CTransaction& tx) +{ + /* + It's possible (very unlikely though) to get 2 conflicting transaction locks approved by the network. + In that case, they will cancel each other out. + + Blocks could have been rejected during this time, which is OK. After they cancel out, the client will + rescan the blocks and find they're acceptable and then take the chain with the most work. + */ + BOOST_FOREACH (const CTxIn& in, tx.vin) { + if (mapLockedInputs.count(in.prevout)) { + if (mapLockedInputs[in.prevout] != tx.GetHash()) { + LogPrintf("SwiftX::CheckForConflictingLocks - found two complete conflicting locks - removing both. %s %s", tx.GetHash().ToString().c_str(), mapLockedInputs[in.prevout].ToString().c_str()); + if (mapTxLocks.count(tx.GetHash())) mapTxLocks[tx.GetHash()].nExpiration = GetTime(); + if (mapTxLocks.count(mapLockedInputs[in.prevout])) mapTxLocks[mapLockedInputs[in.prevout]].nExpiration = GetTime(); + return true; + } + } + } + + return false; +} + +int64_t GetAverageVoteTime() +{ + std::map::iterator it = mapUnknownVotes.begin(); + int64_t total = 0; + int64_t count = 0; + + while (it != mapUnknownVotes.end()) { + total += it->second; + count++; + it++; + } + + return total / count; +} + +void CleanTransactionLocksList() +{ + if (chainActive.Tip() == NULL) return; + + std::map::iterator it = mapTxLocks.begin(); + + while (it != mapTxLocks.end()) { + if (GetTime() > it->second.nExpiration) { //keep them for an hour + LogPrintf("Removing old transaction lock %s\n", it->second.txHash.ToString().c_str()); + + if (mapTxLockReq.count(it->second.txHash)) { + CTransaction& tx = mapTxLockReq[it->second.txHash]; + + BOOST_FOREACH (const CTxIn& in, tx.vin) + mapLockedInputs.erase(in.prevout); + + mapTxLockReq.erase(it->second.txHash); + mapTxLockReqRejected.erase(it->second.txHash); + + BOOST_FOREACH (CConsensusVote& v, it->second.vecConsensusVotes) + mapTxLockVote.erase(v.GetHash()); + } + + mapTxLocks.erase(it++); + } else { + it++; + } + } +} + +uint256 CConsensusVote::GetHash() const +{ + arith_uint256 temp = (UintToArith256)(vinZeronode.prevout.hash) + vinZeronode.prevout.n; + return ArithToUint256(temp + UintToArith256(txHash)); +} + + +bool CConsensusVote::SignatureValid() +{ + std::string errorMessage; + std::string strMessage = txHash.ToString().c_str() + boost::lexical_cast(nBlockHeight); + //LogPrintf("verify strMessage %s \n", strMessage.c_str()); + + CZeronode* pzn = znodeman.Find(vinZeronode); + + if (pzn == NULL) { + LogPrintf("SwiftX::CConsensusVote::SignatureValid() - Unknown Zeronode\n"); + return false; + } + + if (!obfuScationSigner.VerifyMessage(pzn->pubKeyZeronode, vchZeroNodeSignature, strMessage, errorMessage)) { + LogPrintf("SwiftX::CConsensusVote::SignatureValid() - Verify message failed\n"); + return false; + } + + return true; +} + +bool CConsensusVote::Sign() +{ + std::string errorMessage; + + CKey key2; + CPubKey pubkey2; + std::string strMessage = txHash.ToString().c_str() + boost::lexical_cast(nBlockHeight); + //LogPrintf("signing strMessage %s \n", strMessage.c_str()); + //LogPrintf("signing privkey %s \n", strZeroNodePrivKey.c_str()); + + if (!obfuScationSigner.SetKey(strZeroNodePrivKey, errorMessage, key2, pubkey2)) { + LogPrintf("CConsensusVote::Sign() - ERROR: Invalid zeronodeprivkey: '%s'\n", errorMessage.c_str()); + return false; + } + + if (!obfuScationSigner.SignMessage(strMessage, errorMessage, vchZeroNodeSignature, key2)) { + LogPrintf("CConsensusVote::Sign() - Sign message failed"); + return false; + } + + if (!obfuScationSigner.VerifyMessage(pubkey2, vchZeroNodeSignature, strMessage, errorMessage)) { + LogPrintf("CConsensusVote::Sign() - Verify message failed"); + return false; + } + + return true; +} + + +bool CTransactionLock::SignaturesValid() +{ + BOOST_FOREACH (CConsensusVote vote, vecConsensusVotes) { + int n = znodeman.GetZeronodeRank(vote.vinZeronode, vote.nBlockHeight, MIN_SWIFTTX_PROTO_VERSION); + + if (n == -1) { + LogPrintf("CTransactionLock::SignaturesValid() - Unknown Zeronode\n"); + return false; + } + + if (n > SWIFTTX_SIGNATURES_TOTAL) { + LogPrintf("CTransactionLock::SignaturesValid() - Zeronode not in the top %s\n", SWIFTTX_SIGNATURES_TOTAL); + return false; + } + + if (!vote.SignatureValid()) { + LogPrintf("CTransactionLock::SignaturesValid() - Signature not valid\n"); + return false; + } + } + + return true; +} + +void CTransactionLock::AddSignature(CConsensusVote& cv) +{ + vecConsensusVotes.push_back(cv); +} + +int CTransactionLock::CountSignatures() +{ + /* + Only count signatures where the BlockHeight matches the transaction's blockheight. + The votes have no proof it's the correct blockheight + */ + + if (nBlockHeight == 0) return -1; + + int n = 0; + BOOST_FOREACH (CConsensusVote v, vecConsensusVotes) { + if (v.nBlockHeight == nBlockHeight) { + n++; + } + } + return n; +} diff --git a/src/zeronode/swifttx.h b/src/zeronode/swifttx.h new file mode 100644 index 00000000000..bf0bc40426c --- /dev/null +++ b/src/zeronode/swifttx.h @@ -0,0 +1,112 @@ +// Copyright (c) 2009-2012 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef SWIFTTX_H +#define SWIFTTX_H + +#include "base58.h" +#include "key.h" +#include "main.h" +#include "net.h" +#include "zeronode/spork.h" +#include "sync.h" +#include "util.h" + +/* + At 15 signatures, 1/2 of the zeronode network can be owned by + one party without comprimising the security of SwiftX + (1000/2150.0)**10 = 0.00047382219560689856 + (1000/2900.0)**10 = 2.3769498616783657e-05 + + ### getting 5 of 10 signatures w/ 1000 nodes of 2900 + (1000/2900.0)**5 = 0.004875397277841433 +*/ +#define SWIFTTX_SIGNATURES_REQUIRED 6 +#define SWIFTTX_SIGNATURES_TOTAL 10 + +using namespace std; +using namespace boost; + +class CConsensusVote; +class CTransaction; +class CTransactionLock; + +static const int MIN_SWIFTTX_PROTO_VERSION = 70103; + +extern map mapTxLockReq; +extern map mapTxLockReqRejected; +extern map mapTxLockVote; +extern map mapTxLocks; +extern std::map mapLockedInputs; +extern int nCompleteTXLocks; + + +int64_t CreateNewLock(CTransaction tx); + +bool IsIXTXValid(const CTransaction& txCollateral); + +// if two conflicting locks are approved by the network, they will cancel out +bool CheckForConflictingLocks(CTransaction& tx); + +void ProcessMessageSwiftTX(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); + +//check if we need to vote on this transaction +void DoConsensusVote(CTransaction& tx, int64_t nBlockHeight); + +//process consensus vote message +bool ProcessConsensusVote(CNode* pnode, CConsensusVote& ctx); + +// keep transaction locks in memory for an hour +void CleanTransactionLocksList(); + +int64_t GetAverageVoteTime(); + +class CConsensusVote +{ +public: + CTxIn vinZeronode; + uint256 txHash; + int nBlockHeight; + std::vector vchZeroNodeSignature; + + uint256 GetHash() const; + + bool SignatureValid(); + bool Sign(); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(txHash); + READWRITE(vinZeronode); + READWRITE(vchZeroNodeSignature); + READWRITE(nBlockHeight); + } +}; + +class CTransactionLock +{ +public: + int nBlockHeight; + uint256 txHash; + std::vector vecConsensusVotes; + int nExpiration; + int nTimeout; + + bool SignaturesValid(); + int CountSignatures(); + void AddSignature(CConsensusVote& cv); + + uint256 GetHash() + { + return txHash; + } +}; + + +#endif diff --git a/src/zeronode/zeronode-sync.cpp b/src/zeronode/zeronode-sync.cpp new file mode 100644 index 00000000000..cc5c908ec95 --- /dev/null +++ b/src/zeronode/zeronode-sync.cpp @@ -0,0 +1,374 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// clang-format off +#include "main.h" +#include "zeronode/activezeronode.h" +#include "zeronode/zeronode-sync.h" +#include "zeronode/payments.h" +#include "zeronode/budget.h" +#include "zeronode/zeronode.h" +#include "zeronode/zeronodeman.h" +#include "zeronode/spork.h" +#include "util.h" +#include "addrman.h" +// clang-format on + +class CZeronodeSync; +CZeronodeSync zeronodeSync; + +CZeronodeSync::CZeronodeSync() +{ + Reset(); +} + + +void CZeronodeSync::Reset() +{ + lastZeronodeList = 0; + lastZeronodeWinner = 0; + lastBudgetItem = 0; + mapSeenSyncZNB.clear(); + mapSeenSyncZNW.clear(); + mapSeenSyncBudget.clear(); + lastFailure = 0; + nCountFailures = 0; + sumZeronodeList = 0; + sumZeronodeWinner = 0; + sumBudgetItemProp = 0; + sumBudgetItemFin = 0; + countZeronodeList = 0; + countZeronodeWinner = 0; + countBudgetItemProp = 0; + countBudgetItemFin = 0; + RequestedZeronodeAssets = ZERONODE_SYNC_INITIAL; + RequestedZeronodeAttempt = 0; + nAssetSyncStarted = GetTime(); +} + +void CZeronodeSync::AddedZeronodeList(uint256 hash) +{ + if (znodeman.mapSeenZeronodeBroadcast.count(hash)) { + if (mapSeenSyncZNB[hash] < ZERONODE_SYNC_THRESHOLD) { + lastZeronodeList = GetTime(); + mapSeenSyncZNB[hash]++; + } + } else { + lastZeronodeList = GetTime(); + mapSeenSyncZNB.insert(make_pair(hash, 1)); + } +} + +void CZeronodeSync::AddedZeronodeWinner(uint256 hash) +{ + if (zeronodePayments.mapZeronodePayeeVotes.count(hash)) { + if (mapSeenSyncZNW[hash] < ZERONODE_SYNC_THRESHOLD) { + lastZeronodeWinner = GetTime(); + mapSeenSyncZNW[hash]++; + } + } else { + lastZeronodeWinner = GetTime(); + mapSeenSyncZNW.insert(make_pair(hash, 1)); + } +} + +void CZeronodeSync::AddedBudgetItem(uint256 hash) +{ + if (budget.mapSeenZeronodeBudgetProposals.count(hash) || budget.mapSeenZeronodeBudgetVotes.count(hash) || + budget.mapSeenFinalizedBudgets.count(hash) || budget.mapSeenFinalizedBudgetVotes.count(hash)) { + if (mapSeenSyncBudget[hash] < ZERONODE_SYNC_THRESHOLD) { + lastBudgetItem = GetTime(); + mapSeenSyncBudget[hash]++; + } + } else { + lastBudgetItem = GetTime(); + mapSeenSyncBudget.insert(make_pair(hash, 1)); + } +} + +bool CZeronodeSync::IsBudgetPropEmpty() +{ + return sumBudgetItemProp == 0 && countBudgetItemProp > 0; +} + +bool CZeronodeSync::IsBudgetFinEmpty() +{ + return sumBudgetItemFin == 0 && countBudgetItemFin > 0; +} + +void CZeronodeSync::GetNextAsset() +{ + switch (RequestedZeronodeAssets) { + case (ZERONODE_SYNC_INITIAL): + case (ZERONODE_SYNC_FAILED): // should never be used here actually, use Reset() instead + ClearFulfilledRequest(); + RequestedZeronodeAssets = ZERONODE_SYNC_SPORKS; + break; + case (ZERONODE_SYNC_SPORKS): + RequestedZeronodeAssets = ZERONODE_SYNC_LIST; + break; + case (ZERONODE_SYNC_LIST): + RequestedZeronodeAssets = ZERONODE_SYNC_ZNW; + break; + case (ZERONODE_SYNC_ZNW): + RequestedZeronodeAssets = ZERONODE_SYNC_BUDGET; + break; + case (ZERONODE_SYNC_BUDGET): + LogPrintf("CZeronodeSync::GetNextAsset - Sync has finished\n"); + RequestedZeronodeAssets = ZERONODE_SYNC_FINISHED; + break; + } + RequestedZeronodeAttempt = 0; + nAssetSyncStarted = GetTime(); +} + +std::string CZeronodeSync::GetSyncStatus() +{ + switch (zeronodeSync.RequestedZeronodeAssets) { + case ZERONODE_SYNC_INITIAL: + return _("Synchronization pending..."); + case ZERONODE_SYNC_SPORKS: + return _("Synchronizing sporks..."); + case ZERONODE_SYNC_LIST: + return _("Synchronizing zeronodes..."); + case ZERONODE_SYNC_ZNW: + return _("Synchronizing zeronode winners..."); + case ZERONODE_SYNC_BUDGET: + return _("Synchronizing budgets..."); + case ZERONODE_SYNC_FAILED: + return _("Synchronization failed"); + case ZERONODE_SYNC_FINISHED: + return _("Synchronization finished"); + } + return ""; +} + +void CZeronodeSync::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) +{ + if (strCommand == "ssc") { //Sync status count + int nItemID; + int nCount; + vRecv >> nItemID >> nCount; + + if (RequestedZeronodeAssets >= ZERONODE_SYNC_FINISHED) return; + + //this means we will receive no further communication + switch (nItemID) { + case (ZERONODE_SYNC_LIST): + if (nItemID != RequestedZeronodeAssets) return; + sumZeronodeList += nCount; + countZeronodeList++; + break; + case (ZERONODE_SYNC_ZNW): + if (nItemID != RequestedZeronodeAssets) return; + sumZeronodeWinner += nCount; + countZeronodeWinner++; + break; + case (ZERONODE_SYNC_BUDGET_PROP): + if (RequestedZeronodeAssets != ZERONODE_SYNC_BUDGET) return; + sumBudgetItemProp += nCount; + countBudgetItemProp++; + break; + case (ZERONODE_SYNC_BUDGET_FIN): + if (RequestedZeronodeAssets != ZERONODE_SYNC_BUDGET) return; + sumBudgetItemFin += nCount; + countBudgetItemFin++; + break; + } + + LogPrint("zeronode", "CZeronodeSync:ProcessMessage - ssc - got inventory count %d %d\n", nItemID, nCount); + } +} + +void CZeronodeSync::ClearFulfilledRequest() +{ + TRY_LOCK(cs_vNodes, lockRecv); + if (!lockRecv) return; + + BOOST_FOREACH (CNode* pnode, vNodes) { + pnode->ClearFulfilledRequest("getspork"); + pnode->ClearFulfilledRequest("znsync"); + pnode->ClearFulfilledRequest("znwsync"); + pnode->ClearFulfilledRequest("busync"); + } +} + +void CZeronodeSync::Process() +{ + static int tick = 0; + static int syncCount = 0; + + if (tick++ % ZERONODE_SYNC_TIMEOUT != 0) return; + + if (IsSynced()) { + /* + Resync if we lose all zeronodes from sleep/wake or failure to sync originally + */ + if (znodeman.CountEnabled() == 0 ) { + if(syncCount < 2){ + Reset(); + syncCount++; + } + } else + return; + } + + //try syncing again + if (RequestedZeronodeAssets == ZERONODE_SYNC_FAILED && lastFailure + (1 * 60) < GetTime()) { + Reset(); + } else if (RequestedZeronodeAssets == ZERONODE_SYNC_FAILED) { + return; + } + + LogPrint("zeronode", "CZeronodeSync::Process() - tick %d RequestedZeronodeAssets %d\n", tick, RequestedZeronodeAssets); + LogPrint("zeronode", "lastZeronodeList = %d\n", lastZeronodeList); + + if (RequestedZeronodeAssets == ZERONODE_SYNC_INITIAL) GetNextAsset(); + + // sporks synced but blockchain is not, wait until we're almost at a recent block to continue + if (NetworkIdFromCommandLine() != CBaseChainParams::REGTEST && + !IsBlockchainSynced() && RequestedZeronodeAssets > ZERONODE_SYNC_SPORKS) return; + + TRY_LOCK(cs_vNodes, lockRecv); + if (!lockRecv) return; + + BOOST_FOREACH (CNode* pnode, vNodes) { + if (NetworkIdFromCommandLine() == CBaseChainParams::REGTEST) { + if (RequestedZeronodeAttempt <= 2) { + pnode->PushMessage("getsporks"); //get current network sporks + } else if (RequestedZeronodeAttempt < 4) { + znodeman.DsegUpdate(pnode); + } else if (RequestedZeronodeAttempt < 6) { + int nMnCount = znodeman.CountEnabled(); + pnode->PushMessage("znget", nMnCount); //sync payees + uint256 n = uint256(); + pnode->PushMessage("znvs", n); //sync zeronode votes + } else { + RequestedZeronodeAssets = ZERONODE_SYNC_FINISHED; + } + RequestedZeronodeAttempt++; + return; + } + + //set to synced + if (RequestedZeronodeAssets == ZERONODE_SYNC_SPORKS) { + if (pnode->HasFulfilledRequest("getspork")) continue; + pnode->FulfilledRequest("getspork"); + + pnode->PushMessage("getsporks"); //get current network sporks + if (RequestedZeronodeAttempt >= 2) GetNextAsset(); + RequestedZeronodeAttempt++; + + return; + } + + if (pnode->nVersion >= zeronodePayments.GetMinZeronodePaymentsProto()) { + if (RequestedZeronodeAssets == ZERONODE_SYNC_LIST) { + if (lastZeronodeList > 0 && lastZeronodeList < GetTime() - ZERONODE_SYNC_TIMEOUT * 2 && RequestedZeronodeAttempt >= ZERONODE_SYNC_THRESHOLD) { //hasn't received a new item in the last five seconds, so we'll move to the + GetNextAsset(); + return; + } + + if (pnode->HasFulfilledRequest("znsync")) continue; + pnode->FulfilledRequest("znsync"); + + // timeout + if (lastZeronodeList == 0 && + (RequestedZeronodeAttempt >= ZERONODE_SYNC_THRESHOLD * 3 || GetTime() - nAssetSyncStarted > ZERONODE_SYNC_TIMEOUT * 5)) { + if (IsSporkActive(SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT)) { + LogPrintf("CZeronodeSync::Process - ERROR - Sync has failed, will retry later\n"); + RequestedZeronodeAssets = ZERONODE_SYNC_FAILED; + RequestedZeronodeAttempt = 0; + lastFailure = GetTime(); + nCountFailures++; + } else { + GetNextAsset(); + } + return; + } + + if (RequestedZeronodeAttempt >= ZERONODE_SYNC_THRESHOLD * 3) return; + + znodeman.DsegUpdate(pnode); + RequestedZeronodeAttempt++; + return; + } + + if (RequestedZeronodeAssets == ZERONODE_SYNC_ZNW) { + if (lastZeronodeWinner > 0 && lastZeronodeWinner < GetTime() - ZERONODE_SYNC_TIMEOUT * 2 && RequestedZeronodeAttempt >= ZERONODE_SYNC_THRESHOLD) { //hasn't received a new item in the last five seconds, so we'll move to the + GetNextAsset(); + return; + } + + if (pnode->HasFulfilledRequest("znwsync")) continue; + pnode->FulfilledRequest("znwsync"); + + // timeout + if (lastZeronodeWinner == 0 && + (RequestedZeronodeAttempt >= ZERONODE_SYNC_THRESHOLD * 3 || GetTime() - nAssetSyncStarted > ZERONODE_SYNC_TIMEOUT * 5)) { + if (IsSporkActive(SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT)) { + LogPrintf("CZeronodeSync::Process - ERROR - Sync has failed, will retry later\n"); + RequestedZeronodeAssets = ZERONODE_SYNC_FAILED; + RequestedZeronodeAttempt = 0; + lastFailure = GetTime(); + nCountFailures++; + } else { + GetNextAsset(); + } + return; + } + + if (RequestedZeronodeAttempt >= ZERONODE_SYNC_THRESHOLD * 3) return; + + CBlockIndex* pindexPrev = chainActive.Tip(); + if (pindexPrev == NULL) return; + + int nMnCount = znodeman.CountEnabled(); + pnode->PushMessage("znget", nMnCount); //sync payees + RequestedZeronodeAttempt++; + + return; + } + } + + if (pnode->nVersion >= ActiveProtocol()) { + if (RequestedZeronodeAssets == ZERONODE_SYNC_BUDGET) { + + // We'll start rejecting votes if we accidentally get set as synced too soon + if (lastBudgetItem > 0 && lastBudgetItem < GetTime() - ZERONODE_SYNC_TIMEOUT * 2 && RequestedZeronodeAttempt >= ZERONODE_SYNC_THRESHOLD) { + + // Hasn't received a new item in the last five seconds, so we'll move to the + GetNextAsset(); + + // Try to activate our zeronode if possible + activeZeronode.ManageStatus(); + + return; + } + + // timeout + if (lastBudgetItem == 0 && + (RequestedZeronodeAttempt >= ZERONODE_SYNC_THRESHOLD * 3 || GetTime() - nAssetSyncStarted > ZERONODE_SYNC_TIMEOUT * 5)) { + // maybe there is no budgets at all, so just finish syncing + GetNextAsset(); + activeZeronode.ManageStatus(); + return; + } + + if (pnode->HasFulfilledRequest("busync")) continue; + pnode->FulfilledRequest("busync"); + + if (RequestedZeronodeAttempt >= ZERONODE_SYNC_THRESHOLD * 3) return; + + uint256 n = uint256(); + pnode->PushMessage("znvs", n); //sync zeronode votes + RequestedZeronodeAttempt++; + + return; + } + } + } +} diff --git a/src/zeronode/zeronode-sync.h b/src/zeronode/zeronode-sync.h new file mode 100644 index 00000000000..47d2fa5c510 --- /dev/null +++ b/src/zeronode/zeronode-sync.h @@ -0,0 +1,82 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef ZERONODE_SYNC_H +#define ZERONODE_SYNC_H + +#define ZERONODE_SYNC_INITIAL 0 +#define ZERONODE_SYNC_SPORKS 1 +#define ZERONODE_SYNC_LIST 2 +#define ZERONODE_SYNC_ZNW 3 +#define ZERONODE_SYNC_BUDGET 4 +#define ZERONODE_SYNC_BUDGET_PROP 10 +#define ZERONODE_SYNC_BUDGET_FIN 11 +#define ZERONODE_SYNC_FAILED 998 +#define ZERONODE_SYNC_FINISHED 999 + +#define ZERONODE_SYNC_TIMEOUT 5 +#define ZERONODE_SYNC_THRESHOLD 2 + +class CZeronodeSync; +extern CZeronodeSync zeronodeSync; + +// +// CZeronodeSync : Sync zeronode assets in stages +// + +class CZeronodeSync +{ +public: + std::map mapSeenSyncZNB; + std::map mapSeenSyncZNW; + std::map mapSeenSyncBudget; + + int64_t lastZeronodeList; + int64_t lastZeronodeWinner; + int64_t lastBudgetItem; + int64_t lastFailure; + int nCountFailures; + + // sum of all counts + int sumZeronodeList; + int sumZeronodeWinner; + int sumBudgetItemProp; + int sumBudgetItemFin; + // peers that reported counts + int countZeronodeList; + int countZeronodeWinner; + int countBudgetItemProp; + int countBudgetItemFin; + + // Count peers we've requested the list from + int RequestedZeronodeAssets; + int RequestedZeronodeAttempt; + + // Time when current zeronode asset sync started + int64_t nAssetSyncStarted; + + CZeronodeSync(); + + void AddedZeronodeList(uint256 hash); + void AddedZeronodeWinner(uint256 hash); + void AddedBudgetItem(uint256 hash); + void GetNextAsset(); + std::string GetSyncStatus(); + void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); + bool IsBudgetFinEmpty(); + bool IsBudgetPropEmpty(); + + void Reset(); + void Process(); + bool IsFailed() { return RequestedZeronodeAssets == ZERONODE_SYNC_FAILED; } + bool IsBlockchainSynced() { return RequestedZeronodeAssets > ZERONODE_SYNC_SPORKS; } + bool IsZeronodeListSynced() { return RequestedZeronodeAssets > ZERONODE_SYNC_LIST; } + bool IsWinnersListSynced() { return RequestedZeronodeAssets > ZERONODE_SYNC_ZNW; } + bool IsSynced() { return RequestedZeronodeAssets == ZERONODE_SYNC_FINISHED; } + void ClearFulfilledRequest(); +}; + +#endif diff --git a/src/zeronode/zeronode.cpp b/src/zeronode/zeronode.cpp new file mode 100644 index 00000000000..d5b4555add2 --- /dev/null +++ b/src/zeronode/zeronode.cpp @@ -0,0 +1,789 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zeronode/zeronode.h" +#include "addrman.h" +#include "zeronode/zeronodeman.h" +#include "zeronode/obfuscation.h" +#include "sync.h" +#include "util.h" +#include "consensus/validation.h" +#include + +#include "key_io.h" + +// keep track of the scanning errors I've seen +map mapSeenZeronodeScanningErrors; +// cache block hashes as we calculate them +std::map mapCacheBlockHashes; + +//Get the last hash that matches the modulus given. Processed in reverse order +bool GetBlockHash(uint256& hash, int nBlockHeight) +{ + if (chainActive.Tip() == NULL) return false; + + if (nBlockHeight == 0) + nBlockHeight = chainActive.Tip()->nHeight; + + if (mapCacheBlockHashes.count(nBlockHeight)) { + hash = mapCacheBlockHashes[nBlockHeight]; + return true; + } + + const CBlockIndex* BlockLastSolved = chainActive.Tip(); + const CBlockIndex* BlockReading = chainActive.Tip(); + + if (BlockLastSolved == NULL || BlockLastSolved->nHeight == 0 || chainActive.Tip()->nHeight + 1 < nBlockHeight) return false; + + int nBlocksAgo = 0; + if (nBlockHeight > 0) nBlocksAgo = (chainActive.Tip()->nHeight + 1) - nBlockHeight; + assert(nBlocksAgo >= 0); + + int n = 0; + for (unsigned int i = 1; BlockReading && BlockReading->nHeight > 0; i++) { + if (n >= nBlocksAgo) { + hash = BlockReading->GetBlockHash(); + mapCacheBlockHashes[nBlockHeight] = hash; + return true; + } + n++; + + if (BlockReading->pprev == NULL) { + assert(BlockReading); + break; + } + BlockReading = BlockReading->pprev; + } + + return false; +} + +CZeronode::CZeronode() +{ + LOCK(cs); + vin = CTxIn(); + addr = CService(); + pubKeyCollateralAddress = CPubKey(); + pubKeyZeronode = CPubKey(); + sig = std::vector(); + activeState = ZERONODE_ENABLED; + sigTime = GetAdjustedTime(); + lastPing = CZeronodePing(); + cacheInputAge = 0; + cacheInputAgeBlock = 0; + unitTest = false; + allowFreeTx = true; + nActiveState = ZERONODE_ENABLED, + protocolVersion = PROTOCOL_VERSION; + nLastDsq = 0; + nScanningErrorCount = 0; + nLastScanningErrorBlockHeight = 0; + lastTimeChecked = 0; +} + +CZeronode::CZeronode(const CZeronode& other) +{ + LOCK(cs); + vin = other.vin; + addr = other.addr; + pubKeyCollateralAddress = other.pubKeyCollateralAddress; + pubKeyZeronode = other.pubKeyZeronode; + sig = other.sig; + activeState = other.activeState; + sigTime = other.sigTime; + lastPing = other.lastPing; + cacheInputAge = other.cacheInputAge; + cacheInputAgeBlock = other.cacheInputAgeBlock; + unitTest = other.unitTest; + allowFreeTx = other.allowFreeTx; + nActiveState = ZERONODE_ENABLED, + protocolVersion = other.protocolVersion; + nLastDsq = other.nLastDsq; + nScanningErrorCount = other.nScanningErrorCount; + nLastScanningErrorBlockHeight = other.nLastScanningErrorBlockHeight; + lastTimeChecked = 0; +} + +CZeronode::CZeronode(const CZeronodeBroadcast& znb) +{ + LOCK(cs); + vin = znb.vin; + addr = znb.addr; + pubKeyCollateralAddress = znb.pubKeyCollateralAddress; + pubKeyZeronode = znb.pubKeyZeronode; + sig = znb.sig; + activeState = ZERONODE_ENABLED; + sigTime = znb.sigTime; + lastPing = znb.lastPing; + cacheInputAge = 0; + cacheInputAgeBlock = 0; + unitTest = false; + allowFreeTx = true; + nActiveState = ZERONODE_ENABLED, + protocolVersion = znb.protocolVersion; + nLastDsq = znb.nLastDsq; + nScanningErrorCount = 0; + nLastScanningErrorBlockHeight = 0; + lastTimeChecked = 0; +} + +// +// When a new zeronode broadcast is sent, update our information +// +bool CZeronode::UpdateFromNewBroadcast(CZeronodeBroadcast& znb) +{ + if (znb.sigTime > sigTime) { + pubKeyZeronode = znb.pubKeyZeronode; + pubKeyCollateralAddress = znb.pubKeyCollateralAddress; + sigTime = znb.sigTime; + sig = znb.sig; + protocolVersion = znb.protocolVersion; + addr = znb.addr; + lastTimeChecked = 0; + int nDoS = 0; + if (znb.lastPing == CZeronodePing() || (znb.lastPing != CZeronodePing() && znb.lastPing.CheckAndUpdate(nDoS, false))) { + lastPing = znb.lastPing; + znodeman.mapSeenZeronodePing.insert(make_pair(lastPing.GetHash(), lastPing)); + } + return true; + } + return false; +} + +// +// Deterministically calculate a given "score" for a Zeronode depending on how close it's hash is to +// the proof of work for that block. The further away they are the better, the furthest will win the election +// and get paid this block +// +arith_uint256 CZeronode::CalculateScore(const uint256& blockHash) +{ + uint256 aux = ArithToUint256(UintToArith256(vin.prevout.hash) + vin.prevout.n); + + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << blockHash; + arith_uint256 hash2 = UintToArith256(ss.GetHash()); + + CHashWriter ss2(SER_GETHASH, PROTOCOL_VERSION); + ss2 << blockHash; + ss2 << aux; + arith_uint256 hash3 = UintToArith256(ss2.GetHash()); + + return (hash3 > hash2 ? hash3 - hash2 : hash2 - hash3); +} + +void CZeronode::Check(bool forceCheck) +{ + if (ShutdownRequested()) return; + + if (!forceCheck && (GetTime() - lastTimeChecked < ZERONODE_CHECK_SECONDS)) return; + lastTimeChecked = GetTime(); + + + //once spent, stop doing the checks + if (activeState == ZERONODE_VIN_SPENT) return; + + + if (!IsPingedWithin(ZERONODE_REMOVAL_SECONDS)) { + activeState = ZERONODE_REMOVE; + return; + } + + if (!IsPingedWithin(ZERONODE_EXPIRATION_SECONDS)) { + activeState = ZERONODE_EXPIRED; + return; + } + + if (!unitTest) { + CValidationState state; + CMutableTransaction tx = CMutableTransaction(); + CScript scriptPubKey; + if (!GetTestingCollateralScript(Params().ZeronodeDummyAddress(), scriptPubKey)){ + LogPrintf("%s: Failed to get a valid scriptPubkey\n", __func__); + return; + } + + CTxOut vout = CTxOut(9999.99 * COIN, scriptPubKey); + tx.vin.push_back(vin); + tx.vout.push_back(vout); + + { + TRY_LOCK(cs_main, lockMain); + if (!lockMain) return; + + if (!AcceptableInputs(mempool, state, CTransaction(tx), false, NULL)) { + activeState = ZERONODE_VIN_SPENT; + return; + } + } + } + + activeState = ZERONODE_ENABLED; // OK +} + +int64_t CZeronode::SecondsSincePayment() +{ + CScript pubkeyScript; + pubkeyScript = GetScriptForDestination(pubKeyCollateralAddress.GetID()); + + int64_t sec = (GetAdjustedTime() - GetLastPaid()); + int64_t month = 60 * 60 * 24 * 30; + if (sec < month) return sec; //if it's less than 30 days, give seconds + + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << vin; + ss << sigTime; + uint256 hash = ss.GetHash(); + + // return some deterministic value for unknown/unpaid but force it to be more than 30 days old + return month + (UintToArith256(hash)).GetCompact(false); +} + +int64_t CZeronode::GetLastPaid() +{ + CBlockIndex* pindexPrev = chainActive.Tip(); + if (pindexPrev == NULL) return false; + + CScript znpayee; + znpayee = GetScriptForDestination(pubKeyCollateralAddress.GetID()); + + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << vin; + ss << sigTime; + uint256 hash = ss.GetHash(); + + // use a deterministic offset to break a tie -- 2.5 minutes + int64_t nOffset = (UintToArith256(hash)).GetCompact(false) % 150; + + if (chainActive.Tip() == NULL) return false; + + const CBlockIndex* BlockReading = chainActive.Tip(); + + int nMnCount = znodeman.CountEnabled() * 1.25; + int n = 0; + for (unsigned int i = 1; BlockReading && BlockReading->nHeight > 0; i++) { + if (n >= nMnCount) { + return 0; + } + n++; + + if (zeronodePayments.mapZeronodeBlocks.count(BlockReading->nHeight)) { + /* + Search for this payee, with at least 2 votes. This will aid in consensus allowing the network + to converge on the same payees quickly, then keep the same schedule. + */ + if (zeronodePayments.mapZeronodeBlocks[BlockReading->nHeight].HasPayeeWithVotes(znpayee, 2)) { + return BlockReading->nTime + nOffset; + } + } + + if (BlockReading->pprev == NULL) { + assert(BlockReading); + break; + } + BlockReading = BlockReading->pprev; + } + + return 0; +} + +std::string CZeronode::GetStatus() +{ + switch (nActiveState) { + case CZeronode::ZERONODE_PRE_ENABLED: + return "PRE_ENABLED"; + case CZeronode::ZERONODE_ENABLED: + return "ENABLED"; + case CZeronode::ZERONODE_EXPIRED: + return "EXPIRED"; + case CZeronode::ZERONODE_OUTPOINT_SPENT: + return "OUTPOINT_SPENT"; + case CZeronode::ZERONODE_REMOVE: + return "REMOVE"; + case CZeronode::ZERONODE_WATCHDOG_EXPIRED: + return "WATCHDOG_EXPIRED"; + case CZeronode::ZERONODE_POSE_BAN: + return "POSE_BAN"; + default: + return "UNKNOWN"; + } +} + +bool CZeronode::IsValidNetAddr() +{ + // TODO: regtest is fine with any addresses for now, + // should probably be a bit smarter if one day we start to implement tests for this + return NetworkIdFromCommandLine() == CBaseChainParams::REGTEST || + (IsReachable(addr) && addr.IsRoutable()); +} + +CZeronodeBroadcast::CZeronodeBroadcast() +{ + vin = CTxIn(); + addr = CService(); + pubKeyCollateralAddress = CPubKey(); + pubKeyZeronode1 = CPubKey(); + sig = std::vector(); + activeState = ZERONODE_ENABLED; + sigTime = GetAdjustedTime(); + lastPing = CZeronodePing(); + cacheInputAge = 0; + cacheInputAgeBlock = 0; + unitTest = false; + allowFreeTx = true; + protocolVersion = PROTOCOL_VERSION; + nLastDsq = 0; + nScanningErrorCount = 0; + nLastScanningErrorBlockHeight = 0; +} + +CZeronodeBroadcast::CZeronodeBroadcast(CService newAddr, CTxIn newVin, CPubKey pubKeyCollateralAddressNew, CPubKey pubKeyZeronodeNew, int protocolVersionIn) +{ + vin = newVin; + addr = newAddr; + pubKeyCollateralAddress = pubKeyCollateralAddressNew; + pubKeyZeronode = pubKeyZeronodeNew; + sig = std::vector(); + activeState = ZERONODE_ENABLED; + sigTime = GetAdjustedTime(); + lastPing = CZeronodePing(); + cacheInputAge = 0; + cacheInputAgeBlock = 0; + unitTest = false; + allowFreeTx = true; + protocolVersion = protocolVersionIn; + nLastDsq = 0; + nScanningErrorCount = 0; + nLastScanningErrorBlockHeight = 0; +} + +CZeronodeBroadcast::CZeronodeBroadcast(const CZeronode& zn) +{ + vin = zn.vin; + addr = zn.addr; + pubKeyCollateralAddress = zn.pubKeyCollateralAddress; + pubKeyZeronode = zn.pubKeyZeronode; + sig = zn.sig; + activeState = zn.activeState; + sigTime = zn.sigTime; + lastPing = zn.lastPing; + cacheInputAge = zn.cacheInputAge; + cacheInputAgeBlock = zn.cacheInputAgeBlock; + unitTest = zn.unitTest; + allowFreeTx = zn.allowFreeTx; + protocolVersion = zn.protocolVersion; + nLastDsq = zn.nLastDsq; + nScanningErrorCount = zn.nScanningErrorCount; + nLastScanningErrorBlockHeight = zn.nLastScanningErrorBlockHeight; +} + +bool CZeronodeBroadcast::Create(std::string strService, std::string strKeyZeronode, std::string strTxHash, std::string strOutputIndex, std::string& strErrorRet, CZeronodeBroadcast& znbRet, bool fOffline) +{ + CTxIn txin; + CPubKey pubKeyCollateralAddressNew; + CKey keyCollateralAddressNew; + CPubKey pubKeyZeronodeNew; + CKey keyZeronodeNew; + + //need correct blocks to send ping + if (!fOffline && !zeronodeSync.IsBlockchainSynced()) { + strErrorRet = "Sync in progress. Must wait until sync is complete to start Zeronode"; + LogPrint("zeronode","CZeronodeBroadcast::Create -- %s\n", strErrorRet); + return false; + } + + if (!obfuScationSigner.GetKeysFromSecret(strKeyZeronode, keyZeronodeNew, pubKeyZeronodeNew)) { + strErrorRet = strprintf("Invalid zeronode key %s", strKeyZeronode); + LogPrint("zeronode","CZeronodeBroadcast::Create -- %s\n", strErrorRet); + return false; + } + + if (!pwalletMain->GetZeronodeVinAndKeys(txin, pubKeyCollateralAddressNew, keyCollateralAddressNew, strTxHash, strOutputIndex)) { + strErrorRet = strprintf("Could not allocate txin %s:%s for zeronode %s", strTxHash, strOutputIndex, strService); + LogPrint("zeronode","CZeronodeBroadcast::Create -- %s\n", strErrorRet); + return false; + } + + CService service = CService(strService); + int mainnetDefaultPort = Params(CBaseChainParams::MAIN).GetDefaultPort(); + if (NetworkIdFromCommandLine() == CBaseChainParams::MAIN) { + if (service.GetPort() != mainnetDefaultPort) { + strErrorRet = strprintf("Invalid port %u for zeronode %s, only %d is supported on mainnet.", service.GetPort(), strService, mainnetDefaultPort); + LogPrint("zeronode","CZeronodeBroadcast::Create -- %s\n", strErrorRet); + return false; + } + } else if (service.GetPort() == mainnetDefaultPort) { + strErrorRet = strprintf("Invalid port %u for zeronode %s, %d is the only supported on mainnet.", service.GetPort(), strService, mainnetDefaultPort); + LogPrint("zeronode","CZeronodeBroadcast::Create -- %s\n", strErrorRet); + return false; + } + + return Create(txin, CService(strService), keyCollateralAddressNew, pubKeyCollateralAddressNew, keyZeronodeNew, pubKeyZeronodeNew, strErrorRet, znbRet); +} + +bool CZeronodeBroadcast::Create(CTxIn txin, CService service, CKey keyCollateralAddressNew, CPubKey pubKeyCollateralAddressNew, CKey keyZeronodeNew, CPubKey pubKeyZeronodeNew, std::string& strErrorRet, CZeronodeBroadcast& znbRet) +{ + // wait for reindex and/or import to finish + if (fImporting || fReindex) return false; + + LogPrint("zeronode", "CZeronodeBroadcast::Create -- pubKeyCollateralAddressNew = %s, pubKeyZeronodeNew.GetID() = %s\n", + EncodeDestination(pubKeyCollateralAddressNew.GetID()), + pubKeyZeronodeNew.GetID().ToString()); + + CZeronodePing znp(txin); + if (!znp.Sign(keyZeronodeNew, pubKeyZeronodeNew)) { + strErrorRet = strprintf("Failed to sign ping, zeronode=%s", txin.prevout.hash.ToString()); + LogPrint("zeronode","CZeronodeBroadcast::Create -- %s\n", strErrorRet); + znbRet = CZeronodeBroadcast(); + return false; + } + + znbRet = CZeronodeBroadcast(service, txin, pubKeyCollateralAddressNew, pubKeyZeronodeNew, PROTOCOL_VERSION); + + if (!znbRet.IsValidNetAddr()) { + strErrorRet = strprintf("Invalid IP address %s, zeronode=%s", znbRet.addr.ToStringIP (), txin.prevout.hash.ToString()); + LogPrint("zeronode","CZeronodeBroadcast::Create -- %s\n", strErrorRet); + znbRet = CZeronodeBroadcast(); + return false; + } + + znbRet.lastPing = znp; + if (!znbRet.Sign(keyCollateralAddressNew)) { + strErrorRet = strprintf("Failed to sign broadcast, zeronode=%s", txin.prevout.hash.ToString()); + LogPrint("zeronode","CZeronodeBroadcast::Create -- %s\n", strErrorRet); + znbRet = CZeronodeBroadcast(); + return false; + } + + return true; +} + +bool CZeronodeBroadcast::CheckAndUpdate(int& nDos) +{ + // make sure signature isn't in the future (past is OK) + if (sigTime > GetAdjustedTime() + 60 * 60) { + LogPrint("zeronode","znb - Signature rejected, too far into the future %s\n", vin.prevout.hash.ToString()); + nDos = 1; + return false; + } + + std::string vchPubKey(pubKeyCollateralAddress.begin(), pubKeyCollateralAddress.end()); + std::string vchPubKey2(pubKeyZeronode.begin(), pubKeyZeronode.end()); + std::string strMessage = addr.ToString() + boost::lexical_cast(sigTime) + vchPubKey + vchPubKey2 + boost::lexical_cast(protocolVersion); + + if (protocolVersion < zeronodePayments.GetMinZeronodePaymentsProto()) { + LogPrint("zeronode","znb - ignoring outdated Zeronode %s protocol version %d\n", vin.prevout.hash.ToString(), protocolVersion); + return false; + } + + CScript pubkeyScript; + pubkeyScript = GetScriptForDestination(pubKeyCollateralAddress.GetID()); + + if (pubkeyScript.size() != 25) { + LogPrint("zeronode","znb - pubkey the wrong size\n"); + nDos = 100; + return false; + } + + CScript pubkeyScript2; + pubkeyScript2 = GetScriptForDestination(pubKeyZeronode.GetID()); + + if (pubkeyScript2.size() != 25) { + LogPrint("zeronode","znb - pubkey2 the wrong size\n"); + nDos = 100; + return false; + } + + if (!vin.scriptSig.empty()) { + LogPrint("zeronode","znb - Ignore Not Empty ScriptSig %s\n", vin.prevout.hash.ToString()); + return false; + } + + std::string errorMessage = ""; + if (!obfuScationSigner.VerifyMessage(pubKeyCollateralAddress, sig, strMessage, errorMessage)) { + LogPrint("zeronode","znb - Got bad Zeronode address signature\n"); + nDos = 100; + return false; + } + + if (NetworkIdFromCommandLine() == CBaseChainParams::MAIN) { + if (addr.GetPort() != 23801) return false; + } else if (addr.GetPort() == 23801) + return false; + + //search existing Zeronode list, this is where we update existing Zeronodes with new znb broadcasts + CZeronode* pzn = znodeman.Find(vin); + + // no such zeronode, nothing to update + if (pzn == NULL) + return true; + else { + // this broadcast older than we have, it's bad. + if (pzn->sigTime > sigTime) { + LogPrint("zeronode","znb - Bad sigTime %d for Zeronode %s (existing broadcast is at %d)\n", + sigTime, vin.prevout.hash.ToString(), pzn->sigTime); + return false; + } + // zeronode is not enabled yet/already, nothing to update + if (!pzn->IsEnabled()) return true; + } + + // zn.pubkey = pubkey, IsVinAssociatedWithPubkey is validated once below, + // after that they just need to match + if (pzn->pubKeyCollateralAddress == pubKeyCollateralAddress && !pzn->IsBroadcastedWithin(ZERONODE_MIN_MNB_SECONDS)) { + //take the newest entry + LogPrint("zeronode","znb - Got updated entry for %s\n", vin.prevout.hash.ToString()); + if (pzn->UpdateFromNewBroadcast((*this))) { + pzn->Check(); + if (pzn->IsEnabled()) Relay(); + } + zeronodeSync.AddedZeronodeList(GetHash()); + } + + return true; +} + +bool CZeronodeBroadcast::CheckInputsAndAdd(int& nDoS) +{ + LogPrint("zeronode", "CheckInputsAndAdd\n"); + // we are a zeronode with the same vin (i.e. already activated) and this znb is ours (matches our Zeronode privkey) + // so nothing to do here for us + if (fZeroNode && vin.prevout == activeZeronode.vin.prevout && pubKeyZeronode == activeZeronode.pubKeyZeronode) + return true; + + // search existing Zeronode list + CZeronode* pzn = znodeman.Find(vin); + + if (pzn != NULL) { + // nothing to do here if we already know about this zeronode and it's enabled + if (pzn->IsEnabled()) return true; + // if it's not enabled, remove old MN first and continue + else + znodeman.Remove(pzn->vin); + } + + CValidationState state; + CMutableTransaction tx = CMutableTransaction(); + CScript scriptPubKey; + if (!GetTestingCollateralScript(Params().ZeronodeDummyAddress(), scriptPubKey)){ + LogPrintf("%s: Failed to get a valid scriptPubkey\n", __func__); + return false; + } + + CTxOut vout = CTxOut(9999.99 * COIN, scriptPubKey); + tx.vin.push_back(vin); + tx.vout.push_back(vout); + + { + TRY_LOCK(cs_main, lockMain); + if (!lockMain) { + LogPrint("zeronode", "lockMain\n"); + // not znb fault, let it to be checked again later + znodeman.mapSeenZeronodeBroadcast.erase(GetHash()); + zeronodeSync.mapSeenSyncZNB.erase(GetHash()); + return false; + } + + if (!AcceptableInputs(mempool, state, CTransaction(tx), false, NULL)) { + LogPrint("zeronode", "!AcceptableInputs\n"); + //set nDos + state.IsInvalid(nDoS); + return false; + } + } + + LogPrint("zeronode", "znb - Accepted Zeronode entry\n"); + + if (GetInputAge(vin) < ZERONODE_MIN_CONFIRMATIONS) { + LogPrint("zeronode","znb - Input must have at least %d confirmations\n", ZERONODE_MIN_CONFIRMATIONS); + // maybe we miss few blocks, let this znb to be checked again later + znodeman.mapSeenZeronodeBroadcast.erase(GetHash()); + zeronodeSync.mapSeenSyncZNB.erase(GetHash()); + return false; + } + + // verify that sig time is legit in past + // should be at least not earlier than block when 10000 ZER tx got ZERONODE_MIN_CONFIRMATIONS + uint256 hashBlock = uint256(); + CTransaction tx2; + GetTransaction(vin.prevout.hash, tx2, hashBlock, true); + BlockMap::iterator mi = mapBlockIndex.find(hashBlock); + if (mi != mapBlockIndex.end() && (*mi).second) { + CBlockIndex* pMNIndex = (*mi).second; // block for 1000 Zero tx -> 1 confirmation + CBlockIndex* pConfIndex = chainActive[pMNIndex->nHeight + ZERONODE_MIN_CONFIRMATIONS - 1]; // block where tx got ZERONODE_MIN_CONFIRMATIONS + if (pConfIndex->GetBlockTime() > sigTime) { + LogPrint("zeronode","znb - Bad sigTime %d for Zeronode %s (%i conf block is at %d)\n", + sigTime, vin.prevout.hash.ToString(), ZERONODE_MIN_CONFIRMATIONS, pConfIndex->GetBlockTime()); + return false; + } + } + + LogPrint("zeronode","znb - Got NEW Zeronode entry - %s - %lli \n", vin.prevout.hash.ToString(), sigTime); + CZeronode zn(*this); + znodeman.Add(zn); + + // if it matches our Zeronode privkey, then we've been remotely activated + if (pubKeyZeronode == activeZeronode.pubKeyZeronode && protocolVersion == PROTOCOL_VERSION) { + activeZeronode.EnableHotColdZeroNode(vin, addr); + } + + bool isLocal = addr.IsRFC1918() || addr.IsLocal(); + if (NetworkIdFromCommandLine() == CBaseChainParams::REGTEST) isLocal = false; + + if (!isLocal) Relay(); + + return true; +} + +void CZeronodeBroadcast::Relay() +{ + CInv inv(MSG_ZERONODE_ANNOUNCE, GetHash()); + RelayInv(inv); +} + +bool CZeronodeBroadcast::Sign(CKey& keyCollateralAddress) +{ + std::string errorMessage; + + std::string vchPubKey(pubKeyCollateralAddress.begin(), pubKeyCollateralAddress.end()); + std::string vchPubKey2(pubKeyZeronode.begin(), pubKeyZeronode.end()); + + sigTime = GetAdjustedTime(); + + std::string strMessage = addr.ToString() + boost::lexical_cast(sigTime) + vchPubKey + vchPubKey2 + boost::lexical_cast(protocolVersion); + + if (!obfuScationSigner.SignMessage(strMessage, errorMessage, sig, keyCollateralAddress)) { + LogPrint("zeronode","CZeronodeBroadcast::Sign() - Error: %s\n", errorMessage); + return false; + } + + if (!obfuScationSigner.VerifyMessage(pubKeyCollateralAddress, sig, strMessage, errorMessage)) { + LogPrint("zeronode","CZeronodeBroadcast::Sign() - Error: %s\n", errorMessage); + return false; + } + + return true; +} + +CZeronodePing::CZeronodePing() +{ + vin = CTxIn(); + blockHash = uint256(); + sigTime = 0; + vchSig = std::vector(); +} + +CZeronodePing::CZeronodePing(CTxIn& newVin) +{ + vin = newVin; + blockHash = chainActive[chainActive.Height() - 12]->GetBlockHash(); + sigTime = GetAdjustedTime(); + vchSig = std::vector(); +} + + +bool CZeronodePing::Sign(CKey& keyZeronode, CPubKey& pubKeyZeronode) +{ + std::string errorMessage; + std::string strZeroNodeSignMessage; + + sigTime = GetAdjustedTime(); + std::string strMessage = vin.ToString() + blockHash.ToString() + boost::lexical_cast(sigTime); + + if (!obfuScationSigner.SignMessage(strMessage, errorMessage, vchSig, keyZeronode)) { + LogPrint("zeronode","CZeronodePing::Sign() - Error: %s\n", errorMessage); + return false; + } + + if (!obfuScationSigner.VerifyMessage(pubKeyZeronode, vchSig, strMessage, errorMessage)) { + LogPrint("zeronode","CZeronodePing::Sign() - Error: %s\n", errorMessage); + return false; + } + + return true; +} + +bool CZeronodePing::CheckAndUpdate(int& nDos, bool fRequireEnabled) +{ + if (sigTime > GetAdjustedTime() + 60 * 60) { + LogPrint("zeronode","CZeronodePing::CheckAndUpdate - Signature rejected, too far into the future %s\n", vin.prevout.hash.ToString()); + nDos = 1; + return false; + } + + if (sigTime <= GetAdjustedTime() - 60 * 60) { + LogPrint("zeronode","CZeronodePing::CheckAndUpdate - Signature rejected, too far into the past %s - %d %d \n", vin.prevout.hash.ToString(), sigTime, GetAdjustedTime()); + nDos = 1; + return false; + } + + LogPrint("zeronode","CZeronodePing::CheckAndUpdate - New Ping - %s - %lli\n", blockHash.ToString(), sigTime); + + // see if we have this Zeronode + CZeronode* pzn = znodeman.Find(vin); + if (pzn != NULL && pzn->protocolVersion >= zeronodePayments.GetMinZeronodePaymentsProto()) { + if (fRequireEnabled && !pzn->IsEnabled()) return false; + + // LogPrint("zeronode","znping - Found corresponding zn for vin: %s\n", vin.ToString()); + // update only if there is no known ping for this zeronode or + // last ping was more then ZERONODE_MIN_MNP_SECONDS-60 ago comparing to this one + if (!pzn->IsPingedWithin(ZERONODE_MIN_MNP_SECONDS - 60, sigTime)) { + std::string strMessage = vin.ToString() + blockHash.ToString() + boost::lexical_cast(sigTime); + + std::string errorMessage = ""; + if (!obfuScationSigner.VerifyMessage(pzn->pubKeyZeronode, vchSig, strMessage, errorMessage)) { + LogPrint("zeronode","CZeronodePing::CheckAndUpdate - Got bad Zeronode address signature %s\n", vin.prevout.hash.ToString()); + nDos = 33; + return false; + } + + BlockMap::iterator mi = mapBlockIndex.find(blockHash); + if (mi != mapBlockIndex.end() && (*mi).second) { + if ((*mi).second->nHeight < chainActive.Height() - 24) { + LogPrint("zeronode","CZeronodePing::CheckAndUpdate - Zeronode %s block hash %s is too old\n", vin.prevout.hash.ToString(), blockHash.ToString()); + // Do nothing here (no Zeronode update, no znping relay) + // Let this node to be visible but fail to accept znping + + return false; + } + } else { + if (fDebug) LogPrint("zeronode","CZeronodePing::CheckAndUpdate - Zeronode %s block hash %s is unknown\n", vin.prevout.hash.ToString(), blockHash.ToString()); + // maybe we stuck so we shouldn't ban this node, just fail to accept it + // TODO: or should we also request this block? + + return false; + } + + pzn->lastPing = *this; + + //znodeman.mapSeenZeronodeBroadcast.lastPing is probably outdated, so we'll update it + CZeronodeBroadcast znb(*pzn); + uint256 hash = znb.GetHash(); + if (znodeman.mapSeenZeronodeBroadcast.count(hash)) { + znodeman.mapSeenZeronodeBroadcast[hash].lastPing = *this; + } + + pzn->Check(true); + if (!pzn->IsEnabled()) return false; + + LogPrint("zeronode", "CZeronodePing::CheckAndUpdate - Zeronode ping accepted, vin: %s\n", vin.prevout.hash.ToString()); + + Relay(); + return true; + } + LogPrint("zeronode", "CZeronodePing::CheckAndUpdate - Zeronode ping arrived too early, vin: %s\n", vin.prevout.hash.ToString()); + //nDos = 1; //disable, this is happening frequently and causing banned peers + return false; + } + LogPrint("zeronode", "CZeronodePing::CheckAndUpdate - Couldn't find compatible Zeronode entry, vin: %s\n", vin.prevout.hash.ToString()); + + return false; +} + +void CZeronodePing::Relay() +{ + CInv inv(MSG_ZERONODE_PING, GetHash()); + RelayInv(inv); +} diff --git a/src/zeronode/zeronode.h b/src/zeronode/zeronode.h new file mode 100644 index 00000000000..3d139bf7f02 --- /dev/null +++ b/src/zeronode/zeronode.h @@ -0,0 +1,335 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef ZERONODE_H +#define ZERONODE_H + +#include "base58.h" +#include "key.h" +#include "main.h" +#include "net.h" +#include "sync.h" +#include "timedata.h" +#include "util.h" + +#define ZERONODE_MIN_CONFIRMATIONS 15 +#define ZERONODE_MIN_MNP_SECONDS (10 * 60) +#define ZERONODE_MIN_MNB_SECONDS (5 * 60) +#define ZERONODE_PING_SECONDS (5 * 60) +#define ZERONODE_EXPIRATION_SECONDS (120 * 60) +#define ZERONODE_REMOVAL_SECONDS (130 * 60) +#define ZERONODE_CHECK_SECONDS 5 + +using namespace std; + +class CZeronode; +class CZeronodeBroadcast; +class CZeronodePing; +extern map mapCacheBlockHashes; + +bool GetBlockHash(uint256& hash, int nBlockHeight); + + +// +// The Zeronode Ping Class : Contains a different serialize method for sending pings from zeronodes throughout the network +// + +class CZeronodePing +{ +public: + CTxIn vin; + uint256 blockHash; + int64_t sigTime; //znb message times + std::vector vchSig; + //removed stop + + CZeronodePing(); + CZeronodePing(CTxIn& newVin); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(vin); + READWRITE(blockHash); + READWRITE(sigTime); + READWRITE(vchSig); + } + + bool CheckAndUpdate(int& nDos, bool fRequireEnabled = true); + bool Sign(CKey& keyZeronode, CPubKey& pubKeyZeronode); + void Relay(); + + uint256 GetHash() + { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << vin; + ss << sigTime; + return ss.GetHash(); + } + + void swap(CZeronodePing& first, CZeronodePing& second) // nothrow + { + // enable ADL (not necessary in our case, but good practice) + using std::swap; + + // by swapping the members of two classes, + // the two classes are effectively swapped + swap(first.vin, second.vin); + swap(first.blockHash, second.blockHash); + swap(first.sigTime, second.sigTime); + swap(first.vchSig, second.vchSig); + } + + CZeronodePing& operator=(CZeronodePing from) + { + swap(*this, from); + return *this; + } + friend bool operator==(const CZeronodePing& a, const CZeronodePing& b) + { + return a.vin == b.vin && a.blockHash == b.blockHash; + } + friend bool operator!=(const CZeronodePing& a, const CZeronodePing& b) + { + return !(a == b); + } +}; + +// +// The Zeronode Class. For managing the Obfuscation process. It contains the input of the 1000 ZER, signature to prove +// it's the one who own that ip address and code for calculating the payment election. +// +class CZeronode +{ +private: + // critical section to protect the inner data structures + mutable CCriticalSection cs; + int64_t lastTimeChecked; + +public: + enum state { + ZERONODE_PRE_ENABLED, + ZERONODE_ENABLED, + ZERONODE_EXPIRED, + ZERONODE_OUTPOINT_SPENT, + ZERONODE_REMOVE, + ZERONODE_WATCHDOG_EXPIRED, + ZERONODE_POSE_BAN, + ZERONODE_VIN_SPENT, + ZERONODE_POS_ERROR + }; + + CTxIn vin; + CService addr; + CPubKey pubKeyCollateralAddress; + CPubKey pubKeyZeronode; + CPubKey pubKeyCollateralAddress1; + CPubKey pubKeyZeronode1; + std::vector sig; + int activeState; + int64_t sigTime; //znb message time + int cacheInputAge; + int cacheInputAgeBlock; + bool unitTest; + bool allowFreeTx; + int protocolVersion; + int nActiveState; + int64_t nLastDsq; //the dsq count from the last dsq broadcast of this node + int nScanningErrorCount; + int nLastScanningErrorBlockHeight; + CZeronodePing lastPing; + + int64_t nLastDsee; // temporary, do not save. Remove after migration to v12 + int64_t nLastDseep; // temporary, do not save. Remove after migration to v12 + + CZeronode(); + CZeronode(const CZeronode& other); + CZeronode(const CZeronodeBroadcast& znb); + + + void swap(CZeronode& first, CZeronode& second) // nothrow + { + // enable ADL (not necessary in our case, but good practice) + using std::swap; + + // by swapping the members of two classes, + // the two classes are effectively swapped + swap(first.vin, second.vin); + swap(first.addr, second.addr); + swap(first.pubKeyCollateralAddress, second.pubKeyCollateralAddress); + swap(first.pubKeyZeronode, second.pubKeyZeronode); + swap(first.sig, second.sig); + swap(first.activeState, second.activeState); + swap(first.sigTime, second.sigTime); + swap(first.lastPing, second.lastPing); + swap(first.cacheInputAge, second.cacheInputAge); + swap(first.cacheInputAgeBlock, second.cacheInputAgeBlock); + swap(first.unitTest, second.unitTest); + swap(first.allowFreeTx, second.allowFreeTx); + swap(first.protocolVersion, second.protocolVersion); + swap(first.nLastDsq, second.nLastDsq); + swap(first.nScanningErrorCount, second.nScanningErrorCount); + swap(first.nLastScanningErrorBlockHeight, second.nLastScanningErrorBlockHeight); + } + + CZeronode& operator=(CZeronode from) + { + swap(*this, from); + return *this; + } + friend bool operator==(const CZeronode& a, const CZeronode& b) + { + return a.vin == b.vin; + } + friend bool operator!=(const CZeronode& a, const CZeronode& b) + { + return !(a.vin == b.vin); + } + + // CALCULATE A RANK AGAINST OF GIVEN BLOCK + arith_uint256 CalculateScore(const uint256& blockHash); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + LOCK(cs); + + READWRITE(vin); + READWRITE(addr); + READWRITE(pubKeyCollateralAddress); + READWRITE(pubKeyZeronode); + READWRITE(sig); + READWRITE(sigTime); + READWRITE(protocolVersion); + READWRITE(activeState); + READWRITE(lastPing); + READWRITE(cacheInputAge); + READWRITE(cacheInputAgeBlock); + READWRITE(unitTest); + READWRITE(allowFreeTx); + READWRITE(nLastDsq); + READWRITE(nScanningErrorCount); + READWRITE(nLastScanningErrorBlockHeight); + } + + int64_t SecondsSincePayment(); + + bool UpdateFromNewBroadcast(CZeronodeBroadcast& znb); + + inline uint64_t SliceHash(uint256& hash, int slice) + { + uint64_t n = 0; + memcpy(&n, &hash + slice * 64, 64); + return n; + } + + void Check(bool forceCheck = false); + + bool IsBroadcastedWithin(int seconds) + { + return (GetAdjustedTime() - sigTime) < seconds; + } + + bool IsPingedWithin(int seconds, int64_t now = -1) + { + now == -1 ? now = GetAdjustedTime() : now; + + return (lastPing == CZeronodePing()) ? false : now - lastPing.sigTime < seconds; + } + + void Disable() + { + sigTime = 0; + lastPing = CZeronodePing(); + } + + bool IsEnabled() + { + return activeState == ZERONODE_ENABLED; + } + + int GetZeronodeInputAge() + { + if (chainActive.Tip() == NULL) return 0; + + if (cacheInputAge == 0) { + cacheInputAge = GetInputAge(vin); + cacheInputAgeBlock = chainActive.Tip()->nHeight; + } + + return cacheInputAge + (chainActive.Tip()->nHeight - cacheInputAgeBlock); + } + + std::string GetStatus(); + + std::string Status() + { + std::string strStatus = "ACTIVE"; + + if (activeState == CZeronode::ZERONODE_ENABLED) strStatus = "ENABLED"; + if (activeState == CZeronode::ZERONODE_EXPIRED) strStatus = "EXPIRED"; + if (activeState == CZeronode::ZERONODE_VIN_SPENT) strStatus = "VIN_SPENT"; + if (activeState == CZeronode::ZERONODE_REMOVE) strStatus = "REMOVE"; + if (activeState == CZeronode::ZERONODE_POS_ERROR) strStatus = "POS_ERROR"; + + return strStatus; + } + + int64_t GetLastPaid(); + bool IsValidNetAddr(); +}; + + +// +// The Zeronode Broadcast Class : Contains a different serialize method for sending zeronodes through the network +// + +class CZeronodeBroadcast : public CZeronode +{ +public: + CZeronodeBroadcast(); + CZeronodeBroadcast(CService newAddr, CTxIn newVin, CPubKey newPubkey, CPubKey newPubkey2, int protocolVersionIn); + CZeronodeBroadcast(const CZeronode& zn); + + bool CheckAndUpdate(int& nDoS); + bool CheckInputsAndAdd(int& nDos); + bool Sign(CKey& keyCollateralAddress); + void Relay(); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(vin); + READWRITE(addr); + READWRITE(pubKeyCollateralAddress); + READWRITE(pubKeyZeronode); + READWRITE(sig); + READWRITE(sigTime); + READWRITE(protocolVersion); + READWRITE(lastPing); + READWRITE(nLastDsq); + } + + uint256 GetHash() + { + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << sigTime; + ss << pubKeyCollateralAddress; + return ss.GetHash(); + } + + /// Create Zeronode broadcast, needs to be relayed manually after that + static bool Create(CTxIn vin, CService service, CKey keyCollateralAddressNew, CPubKey pubKeyCollateralAddressNew, CKey keyZeronodeNew, CPubKey pubKeyZeronodeNew, std::string& strErrorRet, CZeronodeBroadcast& znbRet); + static bool Create(std::string strService, std::string strKey, std::string strTxHash, std::string strOutputIndex, std::string& strErrorRet, CZeronodeBroadcast& znbRet, bool fOffline = false); +}; + +#endif diff --git a/src/zeronode/zeronodeconfig.cpp b/src/zeronode/zeronodeconfig.cpp new file mode 100644 index 00000000000..6da13a5cc3c --- /dev/null +++ b/src/zeronode/zeronodeconfig.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// clang-format off +#include "net.h" +#include "zeronode/zeronodeconfig.h" +#include "util.h" +#include "ui_interface.h" +#include "chainparamsbase.h" +#include +// clang-format on + +CZeronodeConfig zeronodeConfig; + +void CZeronodeConfig::add(std::string alias, std::string ip, std::string privKey, std::string txHash, std::string outputIndex) +{ + CZeronodeEntry cme(alias, ip, privKey, txHash, outputIndex); + entries.push_back(cme); +} + +bool CZeronodeConfig::read(std::string& strErr) +{ + int linenumber = 1; + boost::filesystem::path pathZeronodeConfigFile = GetZeronodeConfigFile(); + boost::filesystem::ifstream streamConfig(pathZeronodeConfigFile); + + if (!streamConfig.good()) { + FILE* configFile = fopen(pathZeronodeConfigFile.string().c_str(), "a"); + if (configFile != NULL) { + std::string strHeader = "# Zeronode config file\n" + "# Format: alias IP:port zeronodeprivkey collateral_output_txid collateral_output_index\n" + "# Example: zn1 127.0.0.1:23801 93HaYBVUCYjEMeeH1Y4sBGLALQZE1Yc1K64xiqgX37tGBDQL8Xg 2bcd3c84c84f87eaa86e4e56834c92927a07f9e18718810b92e0d0324456a67c 0\n"; + fwrite(strHeader.c_str(), std::strlen(strHeader.c_str()), 1, configFile); + fclose(configFile); + } + return true; // Nothing to read, so just return + } + + for (std::string line; std::getline(streamConfig, line); linenumber++) { + if (line.empty()) continue; + + std::istringstream iss(line); + std::string comment, alias, ip, privKey, txHash, outputIndex; + + if (iss >> comment) { + if (comment.at(0) == '#') continue; + iss.str(line); + iss.clear(); + } + + if (!(iss >> alias >> ip >> privKey >> txHash >> outputIndex)) { + iss.str(line); + iss.clear(); + if (!(iss >> alias >> ip >> privKey >> txHash >> outputIndex)) { + strErr = _("Could not parse zeronode.conf") + "\n" + + strprintf(_("Line: %d"), linenumber) + "\n\"" + line + "\""; + streamConfig.close(); + return false; + } + } + + int port = 0; + std::string hostname = ""; + SplitHostPort(ip, port, hostname); + + if (NetworkIdFromCommandLine() == CBaseChainParams::MAIN) { + if (port != 23801) { + strErr = _("Invalid port detected in zeronode.conf") + "\n" + + strprintf(_("Line: %d"), linenumber) + "\n\"" + line + "\"" + "\n" + + _("(must be 23801 for mainnet)"); + streamConfig.close(); + return false; + } + } else if (port == 23801) { + strErr = _("Invalid port detected in zeronode.conf") + "\n" + + strprintf(_("Line: %d"), linenumber) + "\n\"" + line + "\"" + "\n" + + _("(23801 could be used only on mainnet)"); + streamConfig.close(); + return false; + } + + + add(alias, ip, privKey, txHash, outputIndex); + } + + streamConfig.close(); + return true; +} + +bool CZeronodeConfig::CZeronodeEntry::castOutputIndex(int &n) +{ + try { + n = std::stoi(outputIndex); + } catch (const std::exception e) { + LogPrintf("%s: %s on getOutputIndex\n", __func__, e.what()); + return false; + } + + return true; +} diff --git a/src/zeronode/zeronodeconfig.h b/src/zeronode/zeronodeconfig.h new file mode 100644 index 00000000000..d3012d1656b --- /dev/null +++ b/src/zeronode/zeronodeconfig.h @@ -0,0 +1,122 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef SRC_ZERONODECONFIG_H_ +#define SRC_ZERONODECONFIG_H_ + +#include +#include + +#include +#include + +class CZeronodeConfig; +extern CZeronodeConfig zeronodeConfig; + +class CZeronodeConfig +{ +public: + class CZeronodeEntry + { + private: + std::string alias; + std::string ip; + std::string privKey; + std::string txHash; + std::string outputIndex; + + public: + CZeronodeEntry(std::string alias, std::string ip, std::string privKey, std::string txHash, std::string outputIndex) + { + this->alias = alias; + this->ip = ip; + this->privKey = privKey; + this->txHash = txHash; + this->outputIndex = outputIndex; + } + + const std::string& getAlias() const + { + return alias; + } + + void setAlias(const std::string& alias) + { + this->alias = alias; + } + + const std::string& getOutputIndex() const + { + return outputIndex; + } + + bool castOutputIndex(int& n); + + void setOutputIndex(const std::string& outputIndex) + { + this->outputIndex = outputIndex; + } + + const std::string& getPrivKey() const + { + return privKey; + } + + void setPrivKey(const std::string& privKey) + { + this->privKey = privKey; + } + + const std::string& getTxHash() const + { + return txHash; + } + + void setTxHash(const std::string& txHash) + { + this->txHash = txHash; + } + + const std::string& getIp() const + { + return ip; + } + + void setIp(const std::string& ip) + { + this->ip = ip; + } + }; + + CZeronodeConfig() + { + entries = std::vector(); + } + + void clear(); + bool read(std::string& strErr); + void add(std::string alias, std::string ip, std::string privKey, std::string txHash, std::string outputIndex); + + std::vector& getEntries() + { + return entries; + } + + int getCount() + { + int c = -1; + BOOST_FOREACH (CZeronodeEntry e, entries) { + if (e.getAlias() != "") c++; + } + return c; + } + +private: + std::vector entries; +}; + + +#endif /* SRC_ZERONODECONFIG_H_ */ diff --git a/src/zeronode/zeronodeman.cpp b/src/zeronode/zeronodeman.cpp new file mode 100644 index 00000000000..4101f1225c0 --- /dev/null +++ b/src/zeronode/zeronodeman.cpp @@ -0,0 +1,921 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "zeronode/zeronodeman.h" +#include "zeronode/activezeronode.h" +#include "addrman.h" +#include "zeronode/zeronode.h" +#include "zeronode/obfuscation.h" +#include "zeronode/spork.h" +#include "util.h" +#include "consensus/validation.h" +#include +#include + +#define MN_WINNER_MINIMUM_AGE 8000 // Age in seconds. This should be > ZERONODE_REMOVAL_SECONDS to avoid misconfigured new nodes in the list. + +/** Zeronode manager */ +CZeronodeMan znodeman; + +struct CompareLastPaid { + bool operator()(const pair& t1, + const pair& t2) const + { + return t1.first < t2.first; + } +}; + +struct CompareScoreTxIn { + bool operator()(const pair& t1, + const pair& t2) const + { + return t1.first < t2.first; + } +}; + +struct CompareScoreMN { + bool operator()(const pair& t1, + const pair& t2) const + { + return t1.first < t2.first; + } +}; + +// +// CZeronodeDB +// + +CZeronodeDB::CZeronodeDB() +{ + pathMN = GetDataDir() / "zncache.dat"; + strMagicMessage = "ZeronodeCache"; +} + +bool CZeronodeDB::Write(const CZeronodeMan& znodemanToSave) +{ + int64_t nStart = GetTimeMillis(); + + // serialize, checksum data up to that point, then append checksum + CDataStream ssZeronodes(SER_DISK, CLIENT_VERSION); + ssZeronodes << strMagicMessage; // zeronode cache file specific magic message + ssZeronodes << FLATDATA(Params().MessageStart()); // network specific magic number + ssZeronodes << znodemanToSave; + uint256 hash = Hash(ssZeronodes.begin(), ssZeronodes.end()); + ssZeronodes << hash; + + // open output file, and associate with CAutoFile + FILE* file = fopen(pathMN.string().c_str(), "wb"); + CAutoFile fileout(file, SER_DISK, CLIENT_VERSION); + if (fileout.IsNull()) + return error("%s : Failed to open file %s", __func__, pathMN.string()); + + // Write and commit header, data + try { + fileout << ssZeronodes; + } catch (std::exception& e) { + return error("%s : Serialize or I/O error - %s", __func__, e.what()); + } + // FileCommit(fileout); + fileout.fclose(); + + LogPrint("zeronode","Written info to zncache.dat %dms\n", GetTimeMillis() - nStart); + LogPrint("zeronode"," %s\n", znodemanToSave.ToString()); + + return true; +} + +CZeronodeDB::ReadResult CZeronodeDB::Read(CZeronodeMan& znodemanToLoad, bool fDryRun) +{ + int64_t nStart = GetTimeMillis(); + // open input file, and associate with CAutoFile + FILE* file = fopen(pathMN.string().c_str(), "rb"); + CAutoFile filein(file, SER_DISK, CLIENT_VERSION); + if (filein.IsNull()) { + error("%s : Failed to open file %s", __func__, pathMN.string()); + return FileError; + } + + // use file size to size memory buffer + int fileSize = boost::filesystem::file_size(pathMN); + int dataSize = fileSize - sizeof(uint256); + // Don't try to resize to a negative number if file is small + if (dataSize < 0) + dataSize = 0; + vector vchData; + vchData.resize(dataSize); + uint256 hashIn; + + // read data and checksum from file + try { + filein.read((char*)&vchData[0], dataSize); + filein >> hashIn; + } catch (std::exception& e) { + error("%s : Deserialize or I/O error - %s", __func__, e.what()); + return HashReadError; + } + filein.fclose(); + + CDataStream ssZeronodes(vchData, SER_DISK, CLIENT_VERSION); + + // verify stored checksum matches input data + uint256 hashTmp = Hash(ssZeronodes.begin(), ssZeronodes.end()); + if (hashIn != hashTmp) { + error("%s : Checksum mismatch, data corrupted", __func__); + return IncorrectHash; + } + + unsigned char pchMsgTmp[4]; + std::string strMagicMessageTmp; + try { + // de-serialize file header (zeronode cache file specific magic message) and .. + + ssZeronodes >> strMagicMessageTmp; + + // ... verify the message matches predefined one + if (strMagicMessage != strMagicMessageTmp) { + error("%s : Invalid zeronode cache magic message", __func__); + return IncorrectMagicMessage; + } + + // de-serialize file header (network specific magic number) and .. + ssZeronodes >> FLATDATA(pchMsgTmp); + + // ... verify the network matches ours + if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp))) { + error("%s : Invalid network magic number", __func__); + return IncorrectMagicNumber; + } + // de-serialize data into CZeronodeMan object + ssZeronodes >> znodemanToLoad; + } catch (std::exception& e) { + znodemanToLoad.Clear(); + error("%s : Deserialize or I/O error - %s", __func__, e.what()); + return IncorrectFormat; + } + + LogPrint("zeronode","Loaded info from zncache.dat %dms\n", GetTimeMillis() - nStart); + LogPrint("zeronode"," %s\n", znodemanToLoad.ToString()); + if (!fDryRun) { + LogPrint("zeronode","Zeronode manager - cleaning....\n"); + znodemanToLoad.CheckAndRemove(true); + LogPrint("zeronode","Zeronode manager - result:\n"); + LogPrint("zeronode"," %s\n", znodemanToLoad.ToString()); + } + + return Ok; +} + +void DumpZeronodes() +{ + int64_t nStart = GetTimeMillis(); + + CZeronodeDB zndb; + CZeronodeMan tempZnodeman; + + LogPrint("zeronode","Verifying zncache.dat format...\n"); + CZeronodeDB::ReadResult readResult = zndb.Read(tempZnodeman, true); + // there was an error and it was not an error on file opening => do not proceed + if (readResult == CZeronodeDB::FileError) + LogPrint("zeronode","Missing zeronode cache file - zncache.dat, will try to recreate\n"); + else if (readResult != CZeronodeDB::Ok) { + LogPrint("zeronode","Error reading zncache.dat: "); + if (readResult == CZeronodeDB::IncorrectFormat) + LogPrint("zeronode","magic is ok but data has invalid format, will try to recreate\n"); + else { + LogPrint("zeronode","file format is unknown or invalid, please fix it manually\n"); + return; + } + } + LogPrint("zeronode","Writting info to zncache.dat...\n"); + zndb.Write(znodeman); + + LogPrint("zeronode","Zeronode dump finished %dms\n", GetTimeMillis() - nStart); +} + +CZeronodeMan::CZeronodeMan() +{ + nDsqCount = 0; +} + +bool CZeronodeMan::Add(CZeronode& zn) +{ + LOCK(cs); + + if (!zn.IsEnabled()) + return false; + + CZeronode* pzn = Find(zn.vin); + if (pzn == NULL) { + LogPrint("zeronode", "CZeronodeMan: Adding new Zeronode %s - %i now\n", zn.vin.prevout.hash.ToString(), size() + 1); + vZeronodes.push_back(zn); + return true; + } + + return false; +} + +void CZeronodeMan::AskForZN(CNode* pnode, CTxIn& vin) +{ + std::map::iterator i = mWeAskedForZeronodeListEntry.find(vin.prevout); + if (i != mWeAskedForZeronodeListEntry.end()) { + int64_t t = (*i).second; + if (GetTime() < t) return; // we've asked recently + } + + // ask for the znb info once from the node that sent znp + + LogPrint("zeronode", "CZeronodeMan::AskForZN - Asking node for missing entry, vin: %s\n", vin.prevout.hash.ToString()); + pnode->PushMessage("dseg", vin); + int64_t askAgain = GetTime() + ZERONODE_MIN_MNP_SECONDS; + mWeAskedForZeronodeListEntry[vin.prevout] = askAgain; +} + +void CZeronodeMan::Check() +{ + LOCK(cs); + + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + zn.Check(); + } +} + +void CZeronodeMan::CheckAndRemove(bool forceExpiredRemoval) +{ + Check(); + + LOCK(cs); + + //remove inactive and outdated + vector::iterator it = vZeronodes.begin(); + while (it != vZeronodes.end()) { + if ((*it).activeState == CZeronode::ZERONODE_REMOVE || + (*it).activeState == CZeronode::ZERONODE_VIN_SPENT || + (forceExpiredRemoval && (*it).activeState == CZeronode::ZERONODE_EXPIRED) || + (*it).protocolVersion < zeronodePayments.GetMinZeronodePaymentsProto()) { + LogPrint("zeronode", "CZeronodeMan: Removing inactive Zeronode %s - %i now\n", (*it).vin.prevout.hash.ToString(), size() - 1); + + //erase all of the broadcasts we've seen from this vin + // -- if we missed a few pings and the node was removed, this will allow is to get it back without them + // sending a brand new znb + map::iterator it3 = mapSeenZeronodeBroadcast.begin(); + while (it3 != mapSeenZeronodeBroadcast.end()) { + if ((*it3).second.vin == (*it).vin) { + zeronodeSync.mapSeenSyncZNB.erase((*it3).first); + mapSeenZeronodeBroadcast.erase(it3++); + } else { + ++it3; + } + } + + // allow us to ask for this zeronode again if we see another ping + map::iterator it2 = mWeAskedForZeronodeListEntry.begin(); + while (it2 != mWeAskedForZeronodeListEntry.end()) { + if ((*it2).first == (*it).vin.prevout) { + mWeAskedForZeronodeListEntry.erase(it2++); + } else { + ++it2; + } + } + + it = vZeronodes.erase(it); + } else { + ++it; + } + } + + // check who's asked for the Zeronode list + map::iterator it1 = mAskedUsForZeronodeList.begin(); + while (it1 != mAskedUsForZeronodeList.end()) { + if ((*it1).second < GetTime()) { + mAskedUsForZeronodeList.erase(it1++); + } else { + ++it1; + } + } + + // check who we asked for the Zeronode list + it1 = mWeAskedForZeronodeList.begin(); + while (it1 != mWeAskedForZeronodeList.end()) { + if ((*it1).second < GetTime()) { + mWeAskedForZeronodeList.erase(it1++); + } else { + ++it1; + } + } + + // check which Zeronodes we've asked for + map::iterator it2 = mWeAskedForZeronodeListEntry.begin(); + while (it2 != mWeAskedForZeronodeListEntry.end()) { + if ((*it2).second < GetTime()) { + mWeAskedForZeronodeListEntry.erase(it2++); + } else { + ++it2; + } + } + + // remove expired mapSeenZeronodeBroadcast + map::iterator it3 = mapSeenZeronodeBroadcast.begin(); + while (it3 != mapSeenZeronodeBroadcast.end()) { + if ((*it3).second.lastPing.sigTime < GetTime() - (ZERONODE_REMOVAL_SECONDS * 2)) { + mapSeenZeronodeBroadcast.erase(it3++); + zeronodeSync.mapSeenSyncZNB.erase((*it3).second.GetHash()); + } else { + ++it3; + } + } + + // remove expired mapSeenZeronodePing + map::iterator it4 = mapSeenZeronodePing.begin(); + while (it4 != mapSeenZeronodePing.end()) { + if ((*it4).second.sigTime < GetTime() - (ZERONODE_REMOVAL_SECONDS * 2)) { + mapSeenZeronodePing.erase(it4++); + } else { + ++it4; + } + } +} + +void CZeronodeMan::Clear() +{ + LOCK(cs); + vZeronodes.clear(); + mAskedUsForZeronodeList.clear(); + mWeAskedForZeronodeList.clear(); + mWeAskedForZeronodeListEntry.clear(); + mapSeenZeronodeBroadcast.clear(); + mapSeenZeronodePing.clear(); + nDsqCount = 0; +} + +int CZeronodeMan::stable_size () +{ + int nStable_size = 0; + int nMinProtocol = ActiveProtocol(); + int64_t nZeronode_Min_Age = MN_WINNER_MINIMUM_AGE; + int64_t nZeronode_Age = 0; + + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + if (zn.protocolVersion < nMinProtocol) { + continue; // Skip obsolete versions + } + if (IsSporkActive (SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT)) { + nZeronode_Age = GetAdjustedTime() - zn.sigTime; + if ((nZeronode_Age) < nZeronode_Min_Age) { + continue; // Skip zeronodes younger than (default) 8000 sec (MUST be > ZERONODE_REMOVAL_SECONDS) + } + } + zn.Check (); + if (!zn.IsEnabled ()) + continue; // Skip not-enabled zeronodes + + nStable_size++; + } + + return nStable_size; +} + +int CZeronodeMan::CountEnabled(int protocolVersion) +{ + int i = 0; + protocolVersion = protocolVersion == -1 ? zeronodePayments.GetMinZeronodePaymentsProto() : protocolVersion; + + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + zn.Check(); + if (zn.protocolVersion < protocolVersion || !zn.IsEnabled()) continue; + i++; + } + + return i; +} + +void CZeronodeMan::CountNetworks(int protocolVersion, int& ipv4, int& ipv6, int& onion) +{ + protocolVersion = protocolVersion == -1 ? zeronodePayments.GetMinZeronodePaymentsProto() : protocolVersion; + + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + zn.Check(); + std::string strHost; + int port; + SplitHostPort(zn.addr.ToString(), port, strHost); + CNetAddr node = CNetAddr(strHost, false); + int nNetwork = node.GetNetwork(); + switch (nNetwork) { + case 1 : + ipv4++; + break; + case 2 : + ipv6++; + break; + case 3 : + onion++; + break; + } + } +} + +void CZeronodeMan::DsegUpdate(CNode* pnode) +{ + LOCK(cs); + + if (NetworkIdFromCommandLine() == CBaseChainParams::MAIN) { + if (!(pnode->addr.IsRFC1918() || pnode->addr.IsLocal())) { + std::map::iterator it = mWeAskedForZeronodeList.find(pnode->addr); + if (it != mWeAskedForZeronodeList.end()) { + if (GetTime() < (*it).second) { + LogPrint("zeronode", "dseg - we already asked peer %i for the list; skipping...\n", pnode->GetId()); + return; + } + } + } + } + + pnode->PushMessage("dseg", CTxIn()); + int64_t askAgain = GetTime() + ZERONODES_DSEG_SECONDS; + mWeAskedForZeronodeList[pnode->addr] = askAgain; +} + +CZeronode* CZeronodeMan::Find(const CScript& payee) +{ + LOCK(cs); + CScript payee2; + + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + payee2 = GetScriptForDestination(zn.pubKeyCollateralAddress.GetID()); + if (payee2 == payee) + return &zn; + } + return NULL; +} + +CZeronode* CZeronodeMan::Find(const CTxIn& vin) +{ + LOCK(cs); + + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + if (zn.vin.prevout == vin.prevout) + return &zn; + } + return NULL; +} + + +CZeronode* CZeronodeMan::Find(const CPubKey& pubKeyZeronode) +{ + LOCK(cs); + + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + if (zn.pubKeyZeronode == pubKeyZeronode) + return &zn; + } + return NULL; +} + +// +// Deterministically select the oldest/best zeronode to pay on the network +// +CZeronode* CZeronodeMan::GetNextZeronodeInQueueForPayment(int nBlockHeight, bool fFilterSigTime, int& nCount) +{ + LOCK(cs); + + CZeronode* pBestZeronode = NULL; + std::vector > vecZeronodeLastPaid; + + /* + Make a vector with all of the last paid times + */ + + int nMnCount = CountEnabled(); + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + zn.Check(); + if (!zn.IsEnabled()) continue; + + // //check protocol version + if (zn.protocolVersion < zeronodePayments.GetMinZeronodePaymentsProto()) continue; + + //it's in the list (up to 8 entries ahead of current block to allow propagation) -- so let's skip it + if (zeronodePayments.IsScheduled(zn, nBlockHeight)) continue; + + //it's too new, wait for a cycle + if (fFilterSigTime && zn.sigTime + (nMnCount * 2.6 * 60) > GetAdjustedTime()) continue; + + //make sure it has as many confirmations as there are zeronodes + if (zn.GetZeronodeInputAge() < nMnCount) continue; + + vecZeronodeLastPaid.push_back(make_pair(zn.SecondsSincePayment(), zn.vin)); + } + + nCount = (int)vecZeronodeLastPaid.size(); + + //when the network is in the process of upgrading, don't penalize nodes that recently restarted + if (fFilterSigTime && nCount < nMnCount / 3) return GetNextZeronodeInQueueForPayment(nBlockHeight, false, nCount); + + // Sort them high to low + sort(vecZeronodeLastPaid.rbegin(), vecZeronodeLastPaid.rend(), CompareLastPaid()); + + uint256 blockHash; + if(!GetBlockHash(blockHash, nBlockHeight - 101)) { + LogPrintf("CZeronode::GetNextZeronodeInQueueForPayment -- ERROR: GetBlockHash() failed at nBlockHeight %d\n", nBlockHeight - 101); + return NULL; + } + + // Look at 1/10 of the oldest nodes (by last payment), calculate their scores and pay the best one + // -- This doesn't look at who is being paid in the +8-10 blocks, allowing for double payments very rarely + // -- 1/100 payments should be a double payment on mainnet - (1/(3000/10))*2 + // -- (chance per block * chances before IsScheduled will fire) + // Look at all nodes when node count is equal to ZERONODES_MIN_PAYMENT_COUNT or less + int nMinMnCount = ZERONODES_MIN_PAYMENT_COUNT; + int nTenthNetwork = nMnCount / 10; + if (nMinMnCount > nTenthNetwork) nTenthNetwork = nMinMnCount; + + int nCountTenth = 0; + arith_uint256 nHighest = 0; + BOOST_FOREACH (PAIRTYPE(int64_t, CTxIn) & s, vecZeronodeLastPaid) { + CZeronode* pzn = Find(s.second); + if (!pzn) break; + + arith_uint256 n = pzn->CalculateScore(blockHash); + if (n > nHighest) { + nHighest = n; + pBestZeronode = pzn; + } + nCountTenth++; + if (nCountTenth >= nTenthNetwork) break; + } + return pBestZeronode; +} + +CZeronode* CZeronodeMan::FindRandomNotInVec(std::vector& vecToExclude, int protocolVersion) +{ + LOCK(cs); + + protocolVersion = protocolVersion == -1 ? zeronodePayments.GetMinZeronodePaymentsProto() : protocolVersion; + + int nCountEnabled = CountEnabled(protocolVersion); + LogPrint("zeronode", "CZeronodeMan::FindRandomNotInVec - nCountEnabled - vecToExclude.size() %d\n", nCountEnabled - vecToExclude.size()); + if (nCountEnabled - vecToExclude.size() < 1) return NULL; + + int rand = GetRandInt(nCountEnabled - vecToExclude.size()); + LogPrint("zeronode", "CZeronodeMan::FindRandomNotInVec - rand %d\n", rand); + bool found; + + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + if (zn.protocolVersion < protocolVersion || !zn.IsEnabled()) continue; + found = false; + BOOST_FOREACH (CTxIn& usedVin, vecToExclude) { + if (zn.vin.prevout == usedVin.prevout) { + found = true; + break; + } + } + if (found) continue; + if (--rand < 1) { + return &zn; + } + } + + return NULL; +} + +CZeronode* CZeronodeMan::GetCurrentZeroNode(int mod, int64_t nBlockHeight, int minProtocol) +{ + int64_t score = 0; + CZeronode* winner = NULL; + + //make sure we know about this block + uint256 blockHash = uint256(); + if(!GetBlockHash(blockHash, nBlockHeight)) return NULL; + + // scan for winner + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + zn.Check(); + if (zn.protocolVersion < minProtocol || !zn.IsEnabled()) continue; + + // calculate the score for each Zeronode + arith_uint256 n = zn.CalculateScore(blockHash); + int64_t n2 = n.GetCompact(false); + + // determine the winner + if (n2 > score) { + score = n2; + winner = &zn; + } + } + + return winner; +} + +int CZeronodeMan::GetZeronodeRank(const CTxIn& vin, int64_t nBlockHeight, int minProtocol, bool fOnlyActive) +{ + std::vector > vecZeronodeScores; + int64_t nZeronode_Min_Age = MN_WINNER_MINIMUM_AGE; + int64_t nZeronode_Age = 0; + + //make sure we know about this block + uint256 blockHash = uint256(); + if (!GetBlockHash(blockHash, nBlockHeight)) return -1; + + // scan for winner + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + if (zn.protocolVersion < minProtocol) { + LogPrint("zeronode","Skipping Zeronode with obsolete version %d\n", zn.protocolVersion); + continue; // Skip obsolete versions + } + + if (IsSporkActive(SPORK_8_ZERONODE_PAYMENT_ENFORCEMENT)) { + nZeronode_Age = GetAdjustedTime() - zn.sigTime; + if ((nZeronode_Age) < nZeronode_Min_Age) { + if (fDebug) LogPrint("zeronode","Skipping just activated Zeronode. Age: %ld\n", nZeronode_Age); + continue; // Skip zeronodes younger than (default) 1 hour + } + } + if (fOnlyActive) { + zn.Check(); + if (!zn.IsEnabled()) continue; + } + arith_uint256 n = zn.CalculateScore(blockHash); + int64_t n2 = n.GetCompact(false); + + vecZeronodeScores.push_back(make_pair(n2, zn.vin)); + } + + sort(vecZeronodeScores.rbegin(), vecZeronodeScores.rend(), CompareScoreTxIn()); + + int rank = 0; + BOOST_FOREACH (PAIRTYPE(int64_t, CTxIn) & s, vecZeronodeScores) { + rank++; + if (s.second.prevout == vin.prevout) { + return rank; + } + } + + return -1; +} + +std::vector > CZeronodeMan::GetZeronodeRanks(int64_t nBlockHeight, int minProtocol) +{ + std::vector > vecZeronodeScores; + std::vector > vecZeronodeRanks; + + //make sure we know about this block + uint256 blockHash = uint256(); + if (!GetBlockHash(blockHash, nBlockHeight)) return vecZeronodeRanks; + + // scan for winner + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + zn.Check(); + + if (zn.protocolVersion < minProtocol) continue; + + if (!zn.IsEnabled()) { + vecZeronodeScores.push_back(make_pair(9999, zn)); + continue; + } + + arith_uint256 n = zn.CalculateScore(blockHash); + int64_t n2 = n.GetCompact(false); + + vecZeronodeScores.push_back(make_pair(n2, zn)); + } + + sort(vecZeronodeScores.rbegin(), vecZeronodeScores.rend(), CompareScoreMN()); + + int rank = 0; + BOOST_FOREACH (PAIRTYPE(int64_t, CZeronode) & s, vecZeronodeScores) { + rank++; + vecZeronodeRanks.push_back(make_pair(rank, s.second)); + } + + return vecZeronodeRanks; +} + +CZeronode* CZeronodeMan::GetZeronodeByRank(int nRank, int64_t nBlockHeight, int minProtocol, bool fOnlyActive) +{ + std::vector > vecZeronodeScores; + + uint256 blockHash; + if(!GetBlockHash(blockHash, nBlockHeight)) { + LogPrintf("CZeronode::GetZeronodeByRank -- ERROR: GetBlockHash() failed at nBlockHeight %d\n", nBlockHeight); + return NULL; + } + + // scan for winner + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + if (zn.protocolVersion < minProtocol) continue; + if (fOnlyActive) { + zn.Check(); + if (!zn.IsEnabled()) continue; + } + + arith_uint256 n = zn.CalculateScore(blockHash); + int64_t n2 = n.GetCompact(false); + + vecZeronodeScores.push_back(make_pair(n2, zn.vin)); + } + + sort(vecZeronodeScores.rbegin(), vecZeronodeScores.rend(), CompareScoreTxIn()); + + int rank = 0; + BOOST_FOREACH (PAIRTYPE(int64_t, CTxIn) & s, vecZeronodeScores) { + rank++; + if (rank == nRank) { + return Find(s.second); + } + } + + return NULL; +} + +// void CZeronodeMan::ProcessZeronodeConnections() +// { +// //we don't care about this for regtest +// if (NetworkIdFromCommandLine() == CBaseChainParams::REGTEST) return; +// +// LOCK(cs_vNodes); +// BOOST_FOREACH (CNode* pnode, vNodes) { +// if (pnode->fObfuScationMaster) { +// if (obfuScationPool.pSubmittedToZeronode != NULL && pnode->addr == obfuScationPool.pSubmittedToZeronode->addr) continue; +// LogPrint("zeronode","Closing Zeronode connection peer=%i \n", pnode->GetId()); +// pnode->fObfuScationMaster = false; +// pnode->Release(); +// } +// } +// } + +void CZeronodeMan::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) +{ + if (fLiteMode) return; //disable all Obfuscation/Zeronode related functionality + if (!zeronodeSync.IsBlockchainSynced()) return; + + LOCK(cs_process_message); + + if (strCommand == "znb") { //Zeronode Broadcast + CZeronodeBroadcast znb; + vRecv >> znb; + + if (mapSeenZeronodeBroadcast.count(znb.GetHash())) { //seen + zeronodeSync.AddedZeronodeList(znb.GetHash()); + return; + } + mapSeenZeronodeBroadcast.insert(make_pair(znb.GetHash(), znb)); + + int nDoS = 0; + if (!znb.CheckAndUpdate(nDoS)) { + if (nDoS > 0) + { + Misbehaving(pfrom->GetId(), nDoS); + } + + //failed + return; + } + + // make sure the vout that was signed is related to the transaction that spawned the Zeronode + // - this is expensive, so it's only done once per Zeronode + if (!obfuScationSigner.IsVinAssociatedWithPubkey(znb.vin, znb.pubKeyCollateralAddress)) { + LogPrint("zeronode","znb - Got mismatched pubkey and vin\n"); + Misbehaving(pfrom->GetId(), 33); + return; + } + + // make sure it's still unspent + // - this is checked later by .check() in many places and by ThreadCheckObfuScationPool() + if (znb.CheckInputsAndAdd(nDoS)) { + // use this as a peer + addrman.Add(CAddress(znb.addr), pfrom->addr, 2 * 60 * 60); + zeronodeSync.AddedZeronodeList(znb.GetHash()); + } else { + LogPrint("zeronode","znb - Rejected Zeronode entry %s\n", znb.vin.prevout.hash.ToString()); + + if (nDoS > 0) + { + Misbehaving(pfrom->GetId(), nDoS); + } + } + } + + else if (strCommand == "znp") { //Zeronode Ping + CZeronodePing znp; + vRecv >> znp; + + LogPrint("zeronode", "znp - Zeronode ping, vin: %s\n", znp.vin.prevout.hash.ToString()); + + if (mapSeenZeronodePing.count(znp.GetHash())) return; //seen + mapSeenZeronodePing.insert(make_pair(znp.GetHash(), znp)); + + int nDoS = 0; + if (znp.CheckAndUpdate(nDoS)) return; + + if (nDoS > 0) { + // if anything significant failed, mark that node + Misbehaving(pfrom->GetId(), nDoS); + } else { + // if nothing significant failed, search existing Zeronode list + CZeronode* pzn = Find(znp.vin); + // if it's known, don't ask for the znb, just return + if (pzn != NULL) return; + } + + // something significant is broken or zn is unknown, + // we might have to ask for a zeronode entry once + AskForZN(pfrom, znp.vin); + + } else if (strCommand == "dseg") { //Get Zeronode list or specific entry + + CTxIn vin; + vRecv >> vin; + + if (vin == CTxIn()) { //only should ask for this once + //local network + bool isLocal = (pfrom->addr.IsRFC1918() || pfrom->addr.IsLocal()); + + if (!isLocal && NetworkIdFromCommandLine() == CBaseChainParams::MAIN) { + std::map::iterator i = mAskedUsForZeronodeList.find(pfrom->addr); + if (i != mAskedUsForZeronodeList.end()) { + int64_t t = (*i).second; + if (GetTime() < t) { + Misbehaving(pfrom->GetId(), 34); + LogPrint("zeronode","dseg - peer already asked me for the list\n"); + return; + } + } + int64_t askAgain = GetTime() + ZERONODES_DSEG_SECONDS; + mAskedUsForZeronodeList[pfrom->addr] = askAgain; + } + } //else, asking for a specific node which is ok + + + int nInvCount = 0; + + BOOST_FOREACH (CZeronode& zn, vZeronodes) { + if (zn.addr.IsRFC1918()) continue; //local network + + if (zn.IsEnabled()) { + LogPrint("zeronode", "dseg - Sending Zeronode entry - %s \n", zn.vin.prevout.hash.ToString()); + if (vin == CTxIn() || vin == zn.vin) { + CZeronodeBroadcast znb = CZeronodeBroadcast(zn); + uint256 hash = znb.GetHash(); + pfrom->PushInventory(CInv(MSG_ZERONODE_ANNOUNCE, hash)); + nInvCount++; + + if (!mapSeenZeronodeBroadcast.count(hash)) mapSeenZeronodeBroadcast.insert(make_pair(hash, znb)); + + if (vin == zn.vin) { + LogPrint("zeronode", "dseg - Sent 1 Zeronode entry to peer %i\n", pfrom->GetId()); + return; + } + } + } + } + + if (vin == CTxIn()) { + pfrom->PushMessage("ssc", ZERONODE_SYNC_LIST, nInvCount); + LogPrint("zeronode", "dseg - Sent %d Zeronode entries to peer %i\n", nInvCount, pfrom->GetId()); + } + } +} + +void CZeronodeMan::Remove(CTxIn vin) +{ + LOCK(cs); + + vector::iterator it = vZeronodes.begin(); + while (it != vZeronodes.end()) { + if ((*it).vin == vin) { + LogPrint("zeronode", "CZeronodeMan: Removing Zeronode %s - %i now\n", (*it).vin.prevout.hash.ToString(), size() - 1); + vZeronodes.erase(it); + break; + } + ++it; + } +} + +void CZeronodeMan::UpdateZeronodeList(CZeronodeBroadcast znb) +{ + LOCK(cs); + mapSeenZeronodePing.insert(std::make_pair(znb.lastPing.GetHash(), znb.lastPing)); + mapSeenZeronodeBroadcast.insert(std::make_pair(znb.GetHash(), znb)); + + LogPrint("zeronode","CZeronodeMan::UpdateZeronodeList -- zeronode=%s\n", znb.vin.prevout.ToStringShort()); + + CZeronode* pzn = Find(znb.vin); + if (pzn == NULL) { + CZeronode zn(znb); + if (Add(zn)) { + zeronodeSync.AddedZeronodeList(znb.GetHash()); + } + } else if (pzn->UpdateFromNewBroadcast(znb)) { + zeronodeSync.AddedZeronodeList(znb.GetHash()); + } +} + +std::string CZeronodeMan::ToString() const +{ + std::ostringstream info; + + info << "Zeronodes: " << (int)vZeronodes.size() << ", peers who asked us for Zeronode list: " << (int)mAskedUsForZeronodeList.size() << ", peers we asked for Zeronode list: " << (int)mWeAskedForZeronodeList.size() << ", entries in Zeronode list we asked for: " << (int)mWeAskedForZeronodeListEntry.size() << ", nDsqCount: " << (int)nDsqCount; + + return info.str(); +} diff --git a/src/zeronode/zeronodeman.h b/src/zeronode/zeronodeman.h new file mode 100644 index 00000000000..f790b5ef726 --- /dev/null +++ b/src/zeronode/zeronodeman.h @@ -0,0 +1,162 @@ +// Copyright (c) 2014-2015 The Dash developers +// Copyright (c) 2015-2017 The PIVX developers +// Copyright (c) 2017-2018 The Zero developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef ZERONODEMAN_H +#define ZERONODEMAN_H + +#include "base58.h" +#include "key.h" +#include "main.h" +#include "zeronode/zeronode.h" +#include "net.h" +#include "sync.h" +#include "util.h" + +#define ZERONODES_DUMP_SECONDS (15 * 60) +#define ZERONODES_DSEG_SECONDS (3 * 60 * 60) +#define ZERONODES_MIN_PAYMENT_COUNT 10 + +using namespace std; + +class CZeronodeMan; + +extern CZeronodeMan znodeman; +void DumpZeronodes(); + +/** Access to the MN database (zncache.dat) + */ +class CZeronodeDB +{ +private: + boost::filesystem::path pathMN; + std::string strMagicMessage; + +public: + enum ReadResult { + Ok, + FileError, + HashReadError, + IncorrectHash, + IncorrectMagicMessage, + IncorrectMagicNumber, + IncorrectFormat + }; + + CZeronodeDB(); + bool Write(const CZeronodeMan& znodemanToSave); + ReadResult Read(CZeronodeMan& znodemanToLoad, bool fDryRun = false); +}; + +class CZeronodeMan +{ +private: + // critical section to protect the inner data structures + mutable CCriticalSection cs; + + // critical section to protect the inner data structures specifically on messaging + mutable CCriticalSection cs_process_message; + + // map to hold all MNs + std::vector vZeronodes; + // who's asked for the Zeronode list and the last time + std::map mAskedUsForZeronodeList; + // who we asked for the Zeronode list and the last time + std::map mWeAskedForZeronodeList; + // which Zeronodes we've asked for + std::map mWeAskedForZeronodeListEntry; + +public: + // Keep track of all broadcasts I've seen + map mapSeenZeronodeBroadcast; + // Keep track of all pings I've seen + map mapSeenZeronodePing; + + // keep track of dsq count to prevent zeronodes from gaming obfuscation queue + int64_t nDsqCount; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + LOCK(cs); + READWRITE(vZeronodes); + READWRITE(mAskedUsForZeronodeList); + READWRITE(mWeAskedForZeronodeList); + READWRITE(mWeAskedForZeronodeListEntry); + READWRITE(nDsqCount); + + READWRITE(mapSeenZeronodeBroadcast); + READWRITE(mapSeenZeronodePing); + } + + CZeronodeMan(); + CZeronodeMan(CZeronodeMan& other); + + /// Add an entry + bool Add(CZeronode& zn); + + /// Ask (source) node for znb + void AskForZN(CNode* pnode, CTxIn& vin); + + /// Check all Zeronodes + void Check(); + + /// Check all Zeronodes and remove inactive + void CheckAndRemove(bool forceExpiredRemoval = false); + + /// Clear Zeronode vector + void Clear(); + + int CountEnabled(int protocolVersion = -1); + + void CountNetworks(int protocolVersion, int& ipv4, int& ipv6, int& onion); + + void DsegUpdate(CNode* pnode); + + /// Find an entry + CZeronode* Find(const CScript& payee); + CZeronode* Find(const CTxIn& vin); + CZeronode* Find(const CPubKey& pubKeyZeronode); + + /// Find an entry in the zeronode list that is next to be paid + CZeronode* GetNextZeronodeInQueueForPayment(int nBlockHeight, bool fFilterSigTime, int& nCount); + + /// Find a random entry + CZeronode* FindRandomNotInVec(std::vector& vecToExclude, int protocolVersion = -1); + + /// Get the current winner for this block + CZeronode* GetCurrentZeroNode(int mod = 1, int64_t nBlockHeight = 0, int minProtocol = 0); + + std::vector GetFullZeronodeVector() + { + Check(); + return vZeronodes; + } + + std::vector > GetZeronodeRanks(int64_t nBlockHeight, int minProtocol = 0); + int GetZeronodeRank(const CTxIn& vin, int64_t nBlockHeight, int minProtocol = 0, bool fOnlyActive = true); + CZeronode* GetZeronodeByRank(int nRank, int64_t nBlockHeight, int minProtocol = 0, bool fOnlyActive = true); + + void ProcessZeronodeConnections(); + + void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); + + /// Return the number of (unique) Zeronodes + int size() { return vZeronodes.size(); } + + /// Return the number of Zeronodes older than (default) 8000 seconds + int stable_size (); + + std::string ToString() const; + + void Remove(CTxIn vin); + + /// Update zeronode list and maps using provided CZeronodeBroadcast + void UpdateZeronodeList(CZeronodeBroadcast znb); +}; + +#endif