diff --git a/src/kernel.cpp b/src/kernel.cpp index 90c286c0c..a5069ff9b 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -278,18 +278,33 @@ bool stakeTargetHit(uint256 hashProofOfStake, int64_t nValueIn, uint256 bnTarget //instead of looping outside and reinitializing variables many times, we will give a nTimeTx and also search interval so that we can do all the hashing here bool CheckStakeKernelHash(unsigned int nBits, const CBlock blockFrom, const CTransaction txPrev, const COutPoint prevout, unsigned int& nTimeTx, unsigned int nHashDrift, bool fCheck, uint256& hashProofOfStake, bool fPrintProofOfStake) { - //assign new variables to make it easier to read + //Get stake input amount (output amount of a previous tx) int64_t nValueIn = txPrev.vout[prevout.n].nValue; - unsigned int nTimeBlockFrom = blockFrom.GetBlockTime(); + unsigned int nTimeBlockFrom = blockFrom.GetBlockTime(); // When was the block of the tx we've staked created? + // Ensure we can't stake a tx on same block or any blocks before that if (nTimeTx < nTimeBlockFrom) // Transaction timestamp violation return error("CheckStakeKernelHash() : nTime violation"); - // Consensus implementation of protocol change. + // Default requirement for stake is nStakeMinAge (1 * 60 * 60 for BWK) unsigned int nMinStakeAge = nStakeMinAge; + + // IF spork is enabled (check "spork active") the requiremnt will be changed (12 * 60 * 60 for BWK) if (IsSporkActive(SPORK_23_STAKING_REQUIREMENTS) && nTimeBlockFrom >= GetSporkValue(SPORK_23_STAKING_REQUIREMENTS)) { nMinStakeAge = nStakeMinAgeConsensus; } + + // Bulwark's "Re-Stake". Because Bulwark stores metadata identifying stake in tx we know that previously this was a POS reward + // If you have not previously staked on this input then you will have to wait longer for your stake to mature. + // This penalizes "Stake Grinding" and gives reason to leave stakes alone reducing traffic on the network. + if (IsSporkActive(SPORK_25_BWK_RESTAKE) && nTimeBlockFrom >= GetSporkValue(SPORK_25_BWK_RESTAKE)) { + // Make sure this was a basic stake with no splitthreshold (vout[0]=0 nonstandard,vout[1/2]=pos/mn) + if (!txPrev.IsCoinStake() || txPrev.vout.size() != 3) { + nMinStakeAge *= 2; + } + } + + // Ensure at least nMinStakeAge passed between tx and tx with stake if (nTimeBlockFrom + nMinStakeAge > nTimeTx) // Min age requirement return error("CheckStakeKernelHash() : min age violation - nTimeBlockFrom=%d nStakeMinAge=%d nTimeTx=%d", nTimeBlockFrom, nMinStakeAge, nTimeTx); diff --git a/src/main.cpp b/src/main.cpp index cab822c46..c59fbb28f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -907,6 +907,7 @@ int GetIXConfirmations(uint256 nTXHash) { return 0; } +//@todo This function doesn't seem to be used anywhere, remove? // ppcoin: total coin age spent in transaction, in the unit of coin-days. // Only those coins meeting minimum age requirement counts. As those // transactions not in main chain are not currently indexed so we @@ -915,6 +916,7 @@ int GetIXConfirmations(uint256 nTXHash) { // introduced to help nodes establish a consistent view of the coin // age (trust score) of competing branches. bool GetCoinAge(const CTransaction& tx, const unsigned int nTxTime, uint64_t& nCoinAge) { + uint256 bnCentSecond = 0; // coin age in the unit of cent-seconds nCoinAge = 0; @@ -4225,41 +4227,62 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo if (block.IsProofOfStake()) { // Coinbase output should be empty if proof-of-stake block + // The way POS txs are structured is that the first output of first tx is empty with 0 value if (block.vtx[0].vout.size() != 1 || !block.vtx[0].vout[0].IsEmpty()) return state.DoS(100, error("CheckBlock() : coinbase output not empty for proof-of-stake block")); - // Second transaction must be coinstake, the rest must not be + // Ensure there is only one stake in block and it's 1st tx: if (block.vtx.empty() || !block.vtx[1].IsCoinStake()) return state.DoS(100, error("CheckBlock() : second tx is not coinstake")); + + // Ensure there are no other stakes in tx for (unsigned int i = 2; i < block.vtx.size(); i++) if (block.vtx[i].IsCoinStake()) return state.DoS(100, error("CheckBlock() : more than one coinstake")); - // If consensus level checks are in place. + // After spork is enabled we'll perform additional POS checks + // If the spork is not enabled these conditions are NOT checked if (IsSporkActive(SPORK_23_STAKING_REQUIREMENTS) && block.GetBlockTime() >= GetSporkValue(SPORK_23_STAKING_REQUIREMENTS)) { - // Check for minimum value. - if (block.vtx[1].vout[1].nValue < Params().Stake_MinAmount()) - return state.DoS(100, error("CheckBlock() : stake under min. stake value")); + CAmount minStakeAmount = Params().Stake_MinAmount(); + unsigned int minStakeAge = nStakeMinAgeConsensus; + int minStakeConfirmations = Params().Stake_MinConfirmations(); - // Check for coin age. - // First try finding the previous transaction in database. + + // Get transaction of the staked input CTransaction txPrev; uint256 hashBlockPrev; if (!GetTransaction(block.vtx[1].vin[0].prevout.hash, txPrev, hashBlockPrev, true)) return state.DoS(100, error("CheckBlock() : stake failed to find vin transaction")); - // Find block in map. + + // Bulwark's "Re-Stake". Because Bulwark stores metadata identifying stake in tx we know that previously this was a POS reward + // If you have not previously staked on this input then you will have to wait longer for your stake to mature. + // This penalizes "Stake Grinding" and gives reason to leave stakes alone reducing traffic on the network. + if (IsSporkActive(SPORK_25_BWK_RESTAKE) && block.GetBlockTime() >= GetSporkValue(SPORK_25_BWK_RESTAKE)) { + // Time is doubled if it's not a basic steak (without split) + if (!txPrev.IsCoinStake() || txPrev.vout.size() != 3) { + minStakeAge *= 2; + minStakeConfirmations *= 2; + } + } + // Ensure the output of the stake is above min amount (100 for BWK) + if (block.vtx[1].vout[1].nValue < minStakeAmount) + return state.DoS(100, error("CheckBlock() : stake under min. stake value")); + + // Get block of this transaction (the transaction of staked input) CBlockIndex* pindex = NULL; BlockMap::iterator it = mapBlockIndex.find(hashBlockPrev); if (it != mapBlockIndex.end()) pindex = it->second; else return state.DoS(100, error("CheckBlock() : stake failed to find block index")); - // Check block time vs stake age requirement. - if (pindex->GetBlockHeader().nTime + nStakeMinAgeConsensus > GetAdjustedTime()) + + // Ensure the block age of the staked input block is at least nStakeMinAgeConsensus (12 * 60 * 60 for BWK) + //@todo this should probably look at chainActive.Tip()->GetBlockTime() not local computer GetAdjustedTime() + if (pindex->GetBlockHeader().nTime + minStakeAge > GetAdjustedTime()) return state.DoS(100, error("CheckBlock() : stake under min. stake age")); - - // Check that the prev. stake block has required confirmations by height. - if (chainActive.Tip()->nHeight - pindex->nHeight < Params().Stake_MinConfirmations()) + + // Ensure that the difference between latest block and staked block meets min confirmations requirement (475 for BWK) + if (chainActive.Tip()->nHeight - pindex->nHeight < minStakeConfirmations) return state.DoS(100, error("CheckBlock() : stake under min. required confirmations")); } } diff --git a/src/spork.cpp b/src/spork.cpp index 08ed012af..08be86171 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -134,6 +134,8 @@ int64_t GetSporkValue(int nSporkID) { if (nSporkID == SPORK_21_ENABLE_ZEROCOIN) r = SPORK_21_ENABLE_ZEROCOIN_DEFAULT; if (nSporkID == SPORK_22_ZEROCOIN_MAINTENANCE_MODE) r = SPORK_22_ZEROCOIN_MAINTENANCE_MODE_DEFAULT; if (nSporkID == SPORK_23_STAKING_REQUIREMENTS) r = SPORK_23_STAKING_REQUIREMENTS_DEFAULT; + if (nSporkID == SPORK_24_QUERY_NODES) r = SPORK_24_QUERY_NODES_DEFAULT; + if (nSporkID == SPORK_25_BWK_RESTAKE) r = SPORK_25_BWK_RESTAKE_DEFAULT; if (r == -1) LogPrintf("GetSpork::Unknown Spork %d\n", nSporkID); } @@ -286,6 +288,8 @@ int CSporkManager::GetSporkIDByName(std::string strName) { if (strName == "SPORK_21_ENABLE_ZEROCOIN") return SPORK_21_ENABLE_ZEROCOIN; if (strName == "SPORK_22_ZEROCOIN_MAINTENANCE_MODE") return SPORK_22_ZEROCOIN_MAINTENANCE_MODE; if (strName == "SPORK_23_STAKING_REQUIREMENTS") return SPORK_23_STAKING_REQUIREMENTS; + if (strName == "SPORK_24_QUERY_NODES") return SPORK_24_QUERY_NODES; + if (strName == "SPORK_25_BWK_RESTAKE") return SPORK_25_BWK_RESTAKE; return -1; } @@ -311,6 +315,8 @@ std::string CSporkManager::GetSporkNameByID(int id) { if (id == SPORK_21_ENABLE_ZEROCOIN) return "SPORK_21_ENABLE_ZEROCOIN"; if (id == SPORK_22_ZEROCOIN_MAINTENANCE_MODE) return "SPORK_22_ZEROCOIN_MAINTENANCE_MODE"; if (id == SPORK_23_STAKING_REQUIREMENTS) return "SPORK_23_STAKING_REQUIREMENTS"; + if (id == SPORK_24_QUERY_NODES) return "SPORK_24_QUERY_NODES"; + if (id == SPORK_25_BWK_RESTAKE) return "SPORK_25_BWK_RESTAKE"; return "Unknown"; } diff --git a/src/spork.h b/src/spork.h index 9ba2c16ef..f49c0b44b 100644 --- a/src/spork.h +++ b/src/spork.h @@ -47,6 +47,8 @@ using namespace boost; #define SPORK_21_ENABLE_ZEROCOIN 10020 #define SPORK_22_ZEROCOIN_MAINTENANCE_MODE 10021 #define SPORK_23_STAKING_REQUIREMENTS 10022 +#define SPORK_24_QUERY_NODES 10023 +#define SPORK_25_BWK_RESTAKE 10024 #define SPORK_2_SWIFTTX_DEFAULT 978307200 //2001-1-1 #define SPORK_3_SWIFTTX_BLOCK_FILTERING_DEFAULT 1424217600 //2015-2-18 @@ -73,6 +75,8 @@ using namespace boost; #define SPORK_21_ENABLE_ZEROCOIN_DEFAULT 4070908800 //OFF #define SPORK_22_ZEROCOIN_MAINTENANCE_MODE_DEFAULT 4070908800 //OFF #define SPORK_23_STAKING_REQUIREMENTS_DEFAULT 4070908800 //OFF +#define SPORK_24_QUERY_NODES_DEFAULT 4070908800 //OFF +#define SPORK_25_BWK_RESTAKE_DEFAULT 4070908800 //OFF class CSporkMessage; class CSporkManager; diff --git a/src/test/main_tests.cpp b/src/test/main_tests.cpp index d3a09d47c..69daee372 100644 --- a/src/test/main_tests.cpp +++ b/src/test/main_tests.cpp @@ -17,9 +17,9 @@ BOOST_AUTO_TEST_CASE(subsidy_limit_test) { for (int nHeight = 0; nHeight < 14000000; nHeight += 1000) { /* @TODO fix subsidity, add nBits */ CAmount nSubsidy = GetBlockValue(nHeight); - BOOST_CHECK(nSubsidy <= 50 * COIN); + BOOST_CHECK_MESSAGE(nSubsidy <= 50 * COIN,"nSubsidy is " << nSubsidy << ". Expected 50 * COIN."); nSum += nSubsidy * 1000; - BOOST_CHECK(MoneyRange(nSum)); + BOOST_CHECK_MESSAGE(MoneyRange(nSum),"nSum is " << nSum << ". MaxMoneyOut: " << Params().MaxMoneyOut()); } BOOST_CHECK(nSum == 2099999997690000ULL); } diff --git a/src/wallet.cpp b/src/wallet.cpp index 72cb3813a..d3a18cd85 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1696,6 +1696,9 @@ bool CWallet::SelectStakeCoins(std::setvout[out.i].nValue > nTargetAmount) continue; @@ -1704,8 +1707,17 @@ bool CWallet::SelectStakeCoins(std::setvout[out.i].nValue < nStakeAmount) continue; + //For Bulwark Re-staking since we know if a tx is previous stake it'll be considered re-stake resulting in various advantages + if (IsSporkActive(SPORK_25_BWK_RESTAKE)) { + // Basic restakes will receive rewards 2x faster. (These must not be split) + if (!out.tx->IsCoinStake() || out.tx->vout.size()!=3) { + minStakeAge *= 2; + minStakeDepth *= 2; + } + } + //check that it is matured - if (out.nDepth < (out.tx->IsCoinStake() ? nStakeDepth : 10)) + if (out.nDepth < (out.tx->IsCoinStake() ? minStakeDepth : 10)) continue; //if zerocoinspend, then use the block time @@ -1717,7 +1729,7 @@ bool CWallet::SelectStakeCoins(std::set