diff --git a/configure.ac b/configure.ac index e5f245e048..ea940c8a8d 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_MINOR, 3) -define(_CLIENT_VERSION_REVISION, 1) +define(_CLIENT_VERSION_MINOR, 4) +define(_CLIENT_VERSION_REVISION, 0) define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_RC, 0) define(_CLIENT_VERSION_IS_RELEASE, true) diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index b4266a59a8..112245a7ee 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -45,7 +45,7 @@ static void DeserializeAndCheckBlockTest(benchmark::State& state) CValidationState validationState; CheckContextState ctxState; - bool checked = CheckBlock(block, validationState, chainParams->GetConsensus(), ctxState, false); + bool checked = CheckBlock(block, validationState, chainParams->GetConsensus(), ctxState, false, 413567); assert(checked); } } diff --git a/src/bench/duplicate_inputs.cpp b/src/bench/duplicate_inputs.cpp index a1b4f4148f..f03cf84abd 100644 --- a/src/bench/duplicate_inputs.cpp +++ b/src/bench/duplicate_inputs.cpp @@ -61,7 +61,7 @@ static void DuplicateInputs(benchmark::State& state) while (state.KeepRunning()) { CValidationState cvstate{}; CheckContextState ctxState; - assert(!CheckBlock(block, cvstate, chainparams.GetConsensus(), ctxState, false, false)); + assert(!CheckBlock(block, cvstate, chainparams.GetConsensus(), ctxState, false, nHeight, false)); assert(cvstate.GetRejectReason() == "bad-txns-inputs-duplicate"); } } diff --git a/src/blockencodings.cpp b/src/blockencodings.cpp index 9713e93261..306d18d6d0 100644 --- a/src/blockencodings.cpp +++ b/src/blockencodings.cpp @@ -174,7 +174,7 @@ bool PartiallyDownloadedBlock::IsTxAvailable(size_t index) const { return txn_available[index] != nullptr; } -ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector& vtx_missing) { +ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector& vtx_missing, const int height) { assert(!header.IsNull()); uint256 hash = header.GetHash(); block = header; @@ -200,7 +200,7 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector< CValidationState state; CheckContextState ctxState; - if (!CheckBlock(block, state, Params().GetConsensus(), ctxState, false)) { + if (!CheckBlock(block, state, Params().GetConsensus(), ctxState, false, height)) { // TODO: We really want to just check merkle tree manually here, // but that is expensive, and CheckBlock caches a block's // "checked-status" (in the CBlock?). CBlock should be able to diff --git a/src/blockencodings.h b/src/blockencodings.h index 876c35d057..dc331f417c 100644 --- a/src/blockencodings.h +++ b/src/blockencodings.h @@ -206,7 +206,7 @@ class PartiallyDownloadedBlock { // extra_txn is a list of extra transactions to look at, in form ReadStatus InitData(const CBlockHeaderAndShortTxIDs& cmpctblock, const std::vector>& extra_txn); bool IsTxAvailable(size_t index) const; - ReadStatus FillBlock(CBlock& block, const std::vector& vtx_missing); + ReadStatus FillBlock(CBlock& block, const std::vector& vtx_missing, const int height); }; #endif // DEFI_BLOCKENCODINGS_H diff --git a/src/chain.h b/src/chain.h index 19ef141cc8..d2094ef0e1 100644 --- a/src/chain.h +++ b/src/chain.h @@ -187,7 +187,7 @@ class CBlockIndex uint32_t nBits; // proof-of-stake specific fields - uint64_t height; + uint64_t deprecatedHeight; uint64_t mintedBlocks; uint256 stakeModifier; // hash modifier for proof-of-stake std::vector sig; @@ -222,7 +222,7 @@ class CBlockIndex nTime = 0; nBits = 0; stakeModifier = uint256{}; - height = 0; + deprecatedHeight = 0; mintedBlocks = 0; sig = {}; minterKeyID = {}; @@ -241,7 +241,7 @@ class CBlockIndex hashMerkleRoot = block.hashMerkleRoot; nTime = block.nTime; nBits = block.nBits; - height = block.height; + deprecatedHeight = block.deprecatedHeight; mintedBlocks = block.mintedBlocks; stakeModifier = block.stakeModifier; sig = block.sig; @@ -276,7 +276,7 @@ class CBlockIndex block.nTime = nTime; block.nBits = nBits; block.stakeModifier = stakeModifier; - block.height = height; + block.deprecatedHeight = deprecatedHeight; block.mintedBlocks = mintedBlocks; block.sig = sig; return block; @@ -330,7 +330,7 @@ class CBlockIndex std::sort(pbegin, pend); // Only after FC and when we have a full set of times. - if (height >= Params().GetConsensus().FortCanningHeight && pend - pbegin == nMedianTimeSpan) { + if (nHeight >= Params().GetConsensus().FortCanningHeight && pend - pbegin == nMedianTimeSpan) { // Take the median of the top five. return pbegin[8]; } @@ -424,7 +424,7 @@ class CDiskBlockIndex : public CBlockIndex READWRITE(nTime); READWRITE(nBits); READWRITE(stakeModifier); - READWRITE(height); + READWRITE(deprecatedHeight); READWRITE(mintedBlocks); READWRITE(sig); } @@ -438,7 +438,7 @@ class CDiskBlockIndex : public CBlockIndex block.nTime = nTime; block.nBits = nBits; block.stakeModifier = stakeModifier; - block.height = height; + block.deprecatedHeight = deprecatedHeight; block.mintedBlocks = mintedBlocks; block.sig = sig; diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 40d623073c..07d5d8aeed 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -65,7 +65,7 @@ static CBlock CreateGenesisBlock(const char* pszTimestamp, uint32_t nTime, uint3 genesis.nTime = nTime; genesis.nBits = nBits; genesis.nVersion = nVersion; - genesis.height = 0; + genesis.deprecatedHeight = 0; genesis.stakeModifier = uint256S("0"); genesis.mintedBlocks = 0; genesis.vtx.push_back(MakeTransactionRef(std::move(txNew))); @@ -128,6 +128,8 @@ class CMainParams : public CChainParams { consensus.EunosPayaHeight = 1072000; // Aug 05, 2021. consensus.FortCanningHeight = 1367000; // Nov 15, 2021. consensus.FortCanningMuseumHeight = 1430640; + consensus.FortCanningParkHeight = 1503143; + consensus.FortCanningHillHeight = std::numeric_limits::max(); consensus.GreatWorldHeight = std::numeric_limits::max(); consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @@ -308,6 +310,7 @@ class CMainParams : public CChainParams { {850000, uint256S("2d7d58ae18a74f73b9836a8fffd3f65ce409536e654a6c644ce735215238a004")}, {875000, uint256S("44d3b3ba8e920cef86b7ec096ab0a2e608d9fedc14a59611a76a5e40aa53145e")}, {895741, uint256S("61bc1d73c720990dde43a3fec1f703a222ec5c265e6d491efd60eeec1bdb6dc3")}, + {1505965,uint256S("f7474c805de4f05673df2103bd5d8b8dea09b0d22f808ee957a9ceefc0720609")}, } }; @@ -349,6 +352,8 @@ class CTestNetParams : public CChainParams { consensus.EunosPayaHeight = 463300; consensus.FortCanningHeight = 686200; consensus.FortCanningMuseumHeight = 724000; + consensus.FortCanningParkHeight = std::numeric_limits::max(); + consensus.FortCanningHillHeight = std::numeric_limits::max(); consensus.GreatWorldHeight = std::numeric_limits::max(); consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @@ -532,6 +537,8 @@ class CDevNetParams : public CChainParams { consensus.EunosPayaHeight = 300; consensus.FortCanningHeight = std::numeric_limits::max(); consensus.FortCanningMuseumHeight = std::numeric_limits::max(); + consensus.FortCanningParkHeight = std::numeric_limits::max(); + consensus.FortCanningHillHeight = std::numeric_limits::max(); consensus.GreatWorldHeight = std::numeric_limits::max(); consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @@ -707,6 +714,8 @@ class CRegTestParams : public CChainParams { consensus.EunosPayaHeight = 10000000; consensus.FortCanningHeight = 10000000; consensus.FortCanningMuseumHeight = 10000000; + consensus.FortCanningParkHeight = 10000000; + consensus.FortCanningHillHeight = 10000000; consensus.GreatWorldHeight = 10000000; consensus.pos.diffLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @@ -919,8 +928,10 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args) consensus.EunosKampungHeight = static_cast(eunosHeight.value()); } UpdateHeightValidation("Eunos Paya", "-eunospayaheight", consensus.EunosPayaHeight); - UpdateHeightValidation("Fork canning", "-fortcanningheight", consensus.FortCanningHeight); - UpdateHeightValidation("Fork canning museum", "-fortcanningmuseumheight", consensus.FortCanningMuseumHeight); + UpdateHeightValidation("Fort canning", "-fortcanningheight", consensus.FortCanningHeight); + UpdateHeightValidation("Fort canning museum", "-fortcanningmuseumheight", consensus.FortCanningMuseumHeight); + UpdateHeightValidation("Fort canning park", "-fortcanningparkheight", consensus.FortCanningParkHeight); + UpdateHeightValidation("Fort canning hill", "-fortcanninghillheight", consensus.FortCanningHillHeight); UpdateHeightValidation("Great World", "-greatworldheight", consensus.GreatWorldHeight); if (!args.IsArgSet("-vbparams")) return; diff --git a/src/consensus/params.h b/src/consensus/params.h index 8cac646e44..495d3fe5fa 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -91,6 +91,8 @@ struct Params { int EunosPayaHeight; int FortCanningHeight; int FortCanningMuseumHeight; + int FortCanningParkHeight; + int FortCanningHillHeight; int GreatWorldHeight; /** Foundation share after AMK, normalized to COIN = 100% */ diff --git a/src/init.cpp b/src/init.cpp index 3a7c9ac5d1..d256dc9cea 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -471,6 +471,8 @@ void SetupServerArgs() gArgs.AddArg("-eunospayaheight", "EunosPaya fork activation height (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-fortcanningheight", "Fort Canning fork activation height (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-fortcanningmuseumheight", "Fort Canning Museum fork activation height (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-fortcanningparkheight", "Fort Canning Park fork activation height (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-fortcanninghillheight", "Fort Canning Hill fork activation height (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-greatworldheight", "Great World fork activation height (regtest only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-jellyfish_regtest", "Configure the regtest network for jellyfish testing", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #ifdef USE_UPNP diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index 3da4f2aada..a9d1aea7ba 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -785,7 +785,7 @@ void CCustomCSView::CalcAnchoringTeams(const uint256 & stakeModifier, const CBlo std::map> authMN; std::map> confirmMN; ForEachMasternode([&] (uint256 const & id, CMasternode node) { - if(!node.IsActive(pindexNew->height)) + if(!node.IsActive(pindexNew->nHeight)) return true; // Not in our list of MNs from last week, skip. @@ -820,7 +820,7 @@ void CCustomCSView::CalcAnchoringTeams(const uint256 & stakeModifier, const CBlo { LOCK(cs_main); - SetAnchorTeams(authTeam, confirmTeam, pindexNew->height); + SetAnchorTeams(authTeam, confirmTeam, pindexNew->nHeight); } // Debug logging diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 4968ba30a8..fd02c9da81 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -3146,17 +3146,52 @@ void PopulateVaultHistoryData(CHistoryWriters* writers, CAccountsHistoryWriter& } } + +bool IsDisabledTx(uint32_t height, CustomTxType type, const Consensus::Params& consensus) { + if (height < consensus.FortCanningParkHeight) + return false; + + // ICXCreateOrder = '1', + // ICXMakeOffer = '2', + // ICXSubmitDFCHTLC = '3', + // ICXSubmitEXTHTLC = '4', + // ICXClaimDFCHTLC = '5', + // ICXCloseOrder = '6', + // ICXCloseOffer = '7', + + // Leaving close orders, as withdrawal of existing should be ok? + switch (type) { + case CustomTxType::ICXCreateOrder: + case CustomTxType::ICXMakeOffer: + case CustomTxType::ICXSubmitDFCHTLC: + case CustomTxType::ICXSubmitEXTHTLC: + case CustomTxType::ICXClaimDFCHTLC: + return true; + default: + return false; + } +} + + Res ApplyCustomTx(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, const Consensus::Params& consensus, uint32_t height, uint64_t time, uint32_t txn, CHistoryWriters* writers) { auto res = Res::Ok(); if (tx.IsCoinBase() && height > 0) { // genesis contains custom coinbase txs return res; } std::vector metadata; + + const auto metadataValidation = height >= consensus.FortCanningHeight; + auto txType = GuessCustomTxType(tx, metadata, metadataValidation); if (txType == CustomTxType::None) { return res; } + + if (IsDisabledTx(height, txType, consensus)) { + return Res::ErrCode(CustomTxErrCodes::Fatal, "Disabled custom transaction"); + } + if (metadataValidation && txType == CustomTxType::Reject) { return Res::ErrCode(CustomTxErrCodes::Fatal, "Invalid custom transaction"); } @@ -3365,7 +3400,40 @@ bool IsMempooledCustomTxCreate(const CTxMemPool & pool, const uint256 & txid) return false; } -std::vector CPoolSwap::CalculateSwaps(CCustomCSView& view) { +std::vector CPoolSwap::CalculateSwaps(CCustomCSView& view, bool testOnly) { + + std::vector> poolPaths = CalculatePoolPaths(view); + + // Record best pair + std::pair, CAmount> bestPair{{}, 0}; + + // Loop through all common pairs + for (const auto& path : poolPaths) { + + // Test on copy of view + CCustomCSView dummy(view); + + // Execute pool path + auto res = ExecuteSwap(dummy, path, testOnly); + + // Add error for RPC user feedback + if (!res) { + const auto token = dummy.GetToken(currentID); + if (token) { + errors.emplace_back(token->symbol, res.msg); + } + } + + // Record amount if more than previous or default value + if (res && result > bestPair.second) { + bestPair = {path, result}; + } + } + + return bestPair.first; +} + +std::vector> CPoolSwap::CalculatePoolPaths(CCustomCSView& view) { // For tokens to be traded get all pairs and pool IDs std::multimap fromPoolsID, toPoolsID; @@ -3393,8 +3461,8 @@ std::vector CPoolSwap::CalculateSwaps(CCustomCSView& view) { set_intersection(fromPoolsID.begin(), fromPoolsID.end(), toPoolsID.begin(), toPoolsID.end(), std::inserter(commonPairs, commonPairs.begin()), [](std::pair a, std::pair b) { - return a.first < b.first; - }); + return a.first < b.first; + }); // Loop through all common pairs and record direct pool to pool swaps std::vector> poolPaths; @@ -3425,7 +3493,7 @@ std::vector CPoolSwap::CalculateSwaps(CCustomCSView& view) { // If a pool pairs matches from pair and to pair add it to the pool paths if ((fromIt->first == pool.idTokenA.v && toIt->first == pool.idTokenB.v) || - (fromIt->first == pool.idTokenB.v && toIt->first == pool.idTokenA.v)) { + (fromIt->first == pool.idTokenB.v && toIt->first == pool.idTokenA.v)) { poolPaths.push_back({fromIt->second, id, toIt->second}); } } @@ -3433,36 +3501,14 @@ std::vector CPoolSwap::CalculateSwaps(CCustomCSView& view) { return true; }, {0}); - // Record best pair - std::pair, CAmount> bestPair{{}, 0}; - - // Loop through all common pairs - for (const auto& path : poolPaths) { - - // Test on copy of view - CCustomCSView dummy(view); - - // Execute pool path - auto res = ExecuteSwap(dummy, path); - - // Add error for RPC user feedback - if (!res) { - const auto token = dummy.GetToken(currentID); - if (token) { - errors.emplace_back(token->symbol, res.msg); - } - } - - // Record amount if more than previous or default value - if (res && result > bestPair.second) { - bestPair = {path, result}; - } - } - - return bestPair.first; + // return pool paths + return poolPaths; } -Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs) { +// Note: `testOnly` doesn't update views, and as such can result in a previous price calculations +// for a pool, if used multiple times (or duplicated pool IDs) with the same view. +// testOnly is only meant for one-off tests per well defined view. +Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, bool testOnly) { CTokenAmount swapAmountResult{{},0}; Res poolResult = Res::Ok(); @@ -3488,10 +3534,12 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs) { poolPrice = obj.maxPrice; } - CCustomCSView mnview(view); - mnview.CalculateOwnerRewards(obj.from, height); - mnview.CalculateOwnerRewards(obj.to, height); - mnview.Flush(); + if (!testOnly) { + CCustomCSView mnview(view); + mnview.CalculateOwnerRewards(obj.from, height); + mnview.CalculateOwnerRewards(obj.to, height); + mnview.Flush(); + } for (size_t i{0}; i < poolIDs.size(); ++i) { @@ -3524,14 +3572,21 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs) { // Perform swap poolResult = pool->Swap(swapAmount, poolPrice, [&] (const CTokenAmount &tokenAmount) { + // Save swap amount for next loop + swapAmountResult = tokenAmount; + + // If we're just testing, don't do any balance transfers. + // Just go over pools and return result. The only way this can + // cause inaccurate result is if we go over the same path twice, + // which shouldn't happen in the first place. + if (testOnly) + return Res::Ok(); + auto res = view.SetPoolPair(currentID, height, *pool); if (!res) { return res; } - // Save swap amount for next loop - swapAmountResult = tokenAmount; - CCustomCSView intermediateView(view); // hide interemidiate swaps auto& subView = i == 0 ? view : intermediateView; @@ -3548,7 +3603,7 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs) { } intermediateView.Flush(); - return res; + return res; }, static_cast(height)); if (!poolResult) { diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index 392139d8ff..71453e465e 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -482,8 +482,10 @@ class CPoolSwap { CPoolSwap(const CPoolSwapMessage& obj, uint32_t height) : obj(obj), height(height) {} - std::vector CalculateSwaps(CCustomCSView& view); - Res ExecuteSwap(CCustomCSView& view, std::vector poolIDs); + std::vector CalculateSwaps(CCustomCSView& view, bool testOnly = false); + Res ExecuteSwap(CCustomCSView& view, std::vector poolIDs, bool testOnly = false); + std::vector> CalculatePoolPaths(CCustomCSView& view); + CTokenAmount GetResult() { return CTokenAmount{obj.idTokenTo, result}; }; }; #endif // DEFI_MASTERNODES_MN_CHECKS_H diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index 970e3578d4..9597c5bc86 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -83,7 +83,7 @@ UniValue outputEntryToJSON(COutputEntry const & entry, CBlockIndex const * index UniValue obj(UniValue::VOBJ); obj.pushKV("owner", EncodeDestination(entry.destination)); - obj.pushKV("blockHeight", index->height); + obj.pushKV("blockHeight", index->nHeight); obj.pushKV("blockHash", index->GetBlockHash().GetHex()); obj.pushKV("blockTime", index->GetBlockTime()); if (pwtx->IsCoinBase()) { @@ -155,7 +155,7 @@ static void searchInWallet(CWallet const * pwallet, auto* pwtx = &(*it); auto index = LookupBlockIndex(pwtx->hashBlock); - if (!index || index->height == 0) { // skip genesis block + if (!index || index->nHeight == 0) { // skip genesis block continue; } @@ -1183,10 +1183,10 @@ UniValue listaccounthistory(const JSONRPCRequest& request) { count = limit; searchInWallet(pwallet, account, filter, [&](CBlockIndex const * index, CWalletTx const * pwtx) { - return txs.count(pwtx->GetHash()) || startBlock > index->height || index->height > maxBlockHeight; + return txs.count(pwtx->GetHash()) || startBlock > index->nHeight || index->nHeight > maxBlockHeight; }, [&](COutputEntry const & entry, CBlockIndex const * index, CWalletTx const * pwtx) { - auto& array = ret.emplace(index->height, UniValue::VARR).first->second; + auto& array = ret.emplace(index->nHeight, UniValue::VARR).first->second; array.push_back(outputEntryToJSON(entry, index, pwtx)); return --count != 0; } @@ -1517,7 +1517,7 @@ UniValue accounthistorycount(const JSONRPCRequest& request) { if (shouldSearchInWallet) { searchInWallet(pwallet, owner, filter, [&](CBlockIndex const * index, CWalletTx const * pwtx) { - return txs.count(pwtx->GetHash()) || index->height > currentHeight; + return txs.count(pwtx->GetHash()) || index->nHeight > currentHeight; }, [&count](COutputEntry const &, CBlockIndex const *, CWalletTx const *) { ++count; diff --git a/src/masternodes/rpc_loan.cpp b/src/masternodes/rpc_loan.cpp index 41a536ed88..0e86251a45 100644 --- a/src/masternodes/rpc_loan.cpp +++ b/src/masternodes/rpc_loan.cpp @@ -1122,7 +1122,7 @@ UniValue paybackloan(const JSONRPCRequest& request) { {"metadata", RPCArg::Type::OBJ, RPCArg::Optional::NO, "", { {"vaultId", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Id of vault used for loan"}, - {"from", RPCArg::Type::STR, RPCArg::Optional::NO, "Address containing repayment tokens"}, + {"from", RPCArg::Type::STR, RPCArg::Optional::NO, "Address containing repayment tokens. If \"from\" value is: \"*\" (star), it's means auto-selection accounts from wallet."}, {"amounts", RPCArg::Type::STR, RPCArg::Optional::NO, "Amount in amount@token format."}, }, }, @@ -1167,16 +1167,41 @@ UniValue paybackloan(const JSONRPCRequest& request) { else throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"vaultId\" must be non-null"); - if (!metaObj["from"].isNull()) - loanPayback.from = DecodeScript(metaObj["from"].getValStr()); - else - throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"from\" must not be null"); - if (!metaObj["amounts"].isNull()) loanPayback.amounts = DecodeAmounts(pwallet->chain(), metaObj["amounts"], ""); else throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"amounts\" must not be null"); + if (metaObj["from"].isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER,"Invalid parameters, argument \"from\" must not be null"); + } + + auto fromStr = metaObj["from"].getValStr(); + if (fromStr == "*") { + auto selectedAccounts = SelectAccountsByTargetBalances(GetAllMineAccounts(pwallet), loanPayback.amounts, SelectionPie); + + for (auto& account : selectedAccounts) { + auto it = loanPayback.amounts.balances.begin(); + while (it != loanPayback.amounts.balances.end()) { + if (account.second.balances[it->first] < it->second) { + break; + } + it++; + } + if (it == loanPayback.amounts.balances.end()) { + loanPayback.from = account.first; + break; + } + } + + if (loanPayback.from.empty()) { + throw JSONRPCError(RPC_INVALID_REQUEST, + "Not enough tokens on account, call sendtokenstoaddress to increase it.\n"); + } + } else { + loanPayback.from = DecodeScript(fromStr); + } + if (!::IsMine(*pwallet, loanPayback.from)) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Address (%s) is not owned by the wallet", metaObj["from"].getValStr())); diff --git a/src/masternodes/rpc_masternodes.cpp b/src/masternodes/rpc_masternodes.cpp index 201cd0f40d..0c7c671805 100644 --- a/src/masternodes/rpc_masternodes.cpp +++ b/src/masternodes/rpc_masternodes.cpp @@ -139,7 +139,7 @@ UniValue createmasternode(const JSONRPCRequest& request) bool eunosPaya; { LOCK(cs_main); - eunosPaya = ::ChainActive().Tip()->height >= Params().GetConsensus().EunosPayaHeight; + eunosPaya = ::ChainActive().Tip()->nHeight >= Params().GetConsensus().EunosPayaHeight; } // Get timelock if any @@ -318,7 +318,7 @@ UniValue remforcedrewardaddress(const JSONRPCRequest& request) // Temporarily disabled for 2.2 throw JSONRPCError(RPC_INVALID_REQUEST, "reward address change is disabled for Fort Canning"); - + auto pwallet = GetWallet(request); RPCHelpMan{"remforcedrewardaddress", @@ -540,7 +540,7 @@ UniValue updatemasternode(const JSONRPCRequest& request) bool forkCanning; { LOCK(cs_main); - forkCanning = ::ChainActive().Tip()->height >= Params().GetConsensus().FortCanningHeight; + forkCanning = ::ChainActive().Tip()->nHeight >= Params().GetConsensus().FortCanningHeight; } if (!forkCanning) { @@ -796,15 +796,17 @@ UniValue getmasternodeblocks(const JSONRPCRequest& request) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Masternode not found"); } - auto lastHeight = ::ChainActive().Tip()->height + 1; + auto lastHeight = ::ChainActive().Tip()->nHeight + 1; const auto creationHeight = masternode->creationHeight; int depth{std::numeric_limits::max()}; if (!request.params[1].isNull()) { depth = request.params[1].get_int(); } - UniValue ret(UniValue::VOBJ); + auto currentHeight = ::ChainActive().Height(); + depth = std::min(depth, currentHeight); + auto startBlock = currentHeight - depth; auto masternodeBlocks = [&](const uint256& masternodeID, uint32_t blockHeight) { if (masternodeID != mn_id) { @@ -814,13 +816,17 @@ UniValue getmasternodeblocks(const JSONRPCRequest& request) { if (blockHeight <= creationHeight) { return true; } - - if (auto tip = ::ChainActive()[blockHeight]) { - lastHeight = tip->height; - ret.pushKV(std::to_string(tip->height), tip->GetBlockHash().ToString()); + if (blockHeight <= startBlock) { + return false; + } + auto tip = ::ChainActive()[blockHeight]; + if (tip && depth > 0) { + lastHeight = tip->nHeight; + ret.pushKV(std::to_string(lastHeight), tip->GetBlockHash().ToString()); + depth--; } - return true; + return depth != 0; }; pcustomcsview->ForEachSubNode([&](const SubNodeBlockTimeKey &key, CLazySerialize){ @@ -831,12 +837,12 @@ UniValue getmasternodeblocks(const JSONRPCRequest& request) { return masternodeBlocks(key.masternodeID, key.blockHeight); }, MNBlockTimeKey{mn_id, std::numeric_limits::max()}); - auto tip = ::ChainActive()[std::min(lastHeight, uint64_t(Params().GetConsensus().DakotaCrescentHeight)) - 1]; + auto tip = ::ChainActive()[std::min(lastHeight, Params().GetConsensus().DakotaCrescentHeight) - 1]; - for (; tip && tip->height > creationHeight && depth > 0; tip = tip->pprev, --depth) { + for (; tip && tip->nHeight > creationHeight && depth > 0 && tip->nHeight > startBlock; tip = tip->pprev, --depth) { auto id = pcustomcsview->GetMasternodeIdByOperator(tip->minterKey()); if (id && *id == mn_id) { - ret.pushKV(std::to_string(tip->height), tip->GetBlockHash().ToString()); + ret.pushKV(std::to_string(tip->nHeight), tip->GetBlockHash().ToString()); } } diff --git a/src/masternodes/rpc_poolpair.cpp b/src/masternodes/rpc_poolpair.cpp index 7eb0feed21..9d1065123a 100644 --- a/src/masternodes/rpc_poolpair.cpp +++ b/src/masternodes/rpc_poolpair.cpp @@ -86,6 +86,18 @@ UniValue poolShareToJSON(DCT_ID const & poolId, CScript const & provider, CAmoun return ret; } + +UniValue poolPathsToJSON(std::vector> & poolPaths) { + UniValue paths(UniValue::VARR); + for (auto poolIds : poolPaths) { + UniValue pathObj(UniValue::VARR); + for (auto poolId : poolIds){ + pathObj.push_back(poolId.ToString()); + } + paths.push_back(pathObj); + } + return paths; +} void CheckAndFillPoolSwapMessage(const JSONRPCRequest& request, CPoolSwapMessage &poolSwapMsg) { std::string tokenFrom, tokenTo; UniValue metadataObj = request.params[0].get_obj(); @@ -974,6 +986,17 @@ UniValue testpoolswap(const JSONRPCRequest& request) { "Maximum acceptable price"}, }, }, + { + "path", RPCArg::Type::STR, RPCArg::Optional::OMITTED, + "One of auto/direct (default = direct)\n" + "auto - automatically use composite swap or direct swap as needed.\n" + "direct - uses direct path only or fails.\n" + "Note: The default will be switched to auto in the upcoming versions." + }, + { + "verbose", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, + "Returns estimated composite path when true (default = false)" + }, }, RPCResult{ "\"amount@tokenId\" (string) The string with amount result of poolswap in format AMOUNT@TOKENID.\n" @@ -996,7 +1019,21 @@ UniValue testpoolswap(const JSONRPCRequest& request) { }, }.Check(request); - RPCTypeCheck(request.params, {UniValue::VOBJ}, true); + RPCTypeCheck(request.params, {UniValue::VOBJ, UniValue::VSTR, UniValue::VBOOL}, true); + + std::string path = "direct"; + if (request.params.size() > 1) { + path = request.params[1].get_str(); + if (!(path == "direct" || path == "auto")) + throw JSONRPCError(RPC_INVALID_REQUEST, std::string{"Invalid path"}); + } + + bool verbose = false; + if (request.params.size() > 2) { + verbose = request.params[2].get_bool(); + } + + UniValue pools{UniValue::VARR}; CPoolSwapMessage poolSwapMsg{}; CheckAndFillPoolSwapMessage(request, poolSwapMsg); @@ -1008,22 +1045,57 @@ UniValue testpoolswap(const JSONRPCRequest& request) { CCustomCSView mnview_dummy(*pcustomcsview); // create dummy cache for test state writing int targetHeight = ::ChainActive().Height() + 1; - auto poolPair = mnview_dummy.GetPoolPair(poolSwapMsg.idTokenFrom, poolSwapMsg.idTokenTo); - CPoolPair pp = poolPair->second; - res = pp.Swap({poolSwapMsg.idTokenFrom, poolSwapMsg.amountFrom}, poolSwapMsg.maxPrice, [&] (const CTokenAmount &tokenAmount) { - auto resPP = mnview_dummy.SetPoolPair(poolPair->first, targetHeight, pp); - if (!resPP) { - return resPP; - } + // If no direct swap found search for composite swap + if (path == "direct") { + auto poolPair = mnview_dummy.GetPoolPair(poolSwapMsg.idTokenFrom, poolSwapMsg.idTokenTo); + if (!poolPair) + throw JSONRPCError(RPC_INVALID_REQUEST, std::string{"Direct pool pair not found. Use 'auto' mode to use composite swap."}); + + CPoolPair pp = poolPair->second; + res = pp.Swap({poolSwapMsg.idTokenFrom, poolSwapMsg.amountFrom}, poolSwapMsg.maxPrice, [&] (const CTokenAmount &tokenAmount) { + auto resPP = mnview_dummy.SetPoolPair(poolPair->first, targetHeight, pp); + if (!resPP) { + return resPP; + } + + return Res::Ok(tokenAmount.ToString()); + }, targetHeight >= Params().GetConsensus().BayfrontGardensHeight); - return Res::Ok(tokenAmount.ToString()); - }, targetHeight >= Params().GetConsensus().BayfrontGardensHeight); + if (!res) + throw JSONRPCError(RPC_VERIFY_ERROR, res.msg); - if (!res) - throw JSONRPCError(RPC_VERIFY_ERROR, res.msg); + pools.push_back(poolPair->first.ToString()); + } else { + auto compositeSwap = CPoolSwap(poolSwapMsg, targetHeight); + std::vector poolIds = compositeSwap.CalculateSwaps(mnview_dummy, true); + res = compositeSwap.ExecuteSwap(mnview_dummy, poolIds, true); + if (!res) { + std::string errorMsg{"Cannot find usable pool pair."}; + if (!compositeSwap.errors.empty()) { + errorMsg += " Details: ("; + for (size_t i{0}; i < compositeSwap.errors.size(); ++i) { + errorMsg += '"' + compositeSwap.errors[i].first + "\":\"" + compositeSwap.errors[i].second + '"' + (i + 1 < compositeSwap.errors.size() ? "," : ""); + } + errorMsg += ')'; + } + throw JSONRPCError(RPC_INVALID_REQUEST, errorMsg); + } + for (const auto& id : poolIds) { + pools.push_back(id.ToString()); + } + res.msg = compositeSwap.GetResult().ToString(); + } + } + if (verbose) { + UniValue swapObj{UniValue::VOBJ}; + swapObj.pushKV("path", path); + swapObj.pushKV("pools", pools); + swapObj.pushKV("amount", res.msg); + return swapObj; } - return UniValue(res.msg); + + return res.msg; } UniValue listpoolshares(const JSONRPCRequest& request) { @@ -1123,19 +1195,19 @@ UniValue listpoolshares(const JSONRPCRequest& request) { } static const CRPCCommand commands[] = -{ -// category name actor (function) params -// ------------- ----------------------- --------------------- ---------- - {"poolpair", "listpoolpairs", &listpoolpairs, {"pagination", "verbose"}}, - {"poolpair", "getpoolpair", &getpoolpair, {"key", "verbose" }}, - {"poolpair", "addpoolliquidity", &addpoolliquidity, {"from", "shareAddress", "inputs"}}, - {"poolpair", "removepoolliquidity", &removepoolliquidity, {"from", "amount", "inputs"}}, - {"poolpair", "createpoolpair", &createpoolpair, {"metadata", "inputs"}}, - {"poolpair", "updatepoolpair", &updatepoolpair, {"metadata", "inputs"}}, - {"poolpair", "poolswap", &poolswap, {"metadata", "inputs"}}, - {"poolpair", "compositeswap", &compositeswap, {"metadata", "inputs"}}, - {"poolpair", "listpoolshares", &listpoolshares, {"pagination", "verbose", "is_mine_only"}}, - {"poolpair", "testpoolswap", &testpoolswap, {"metadata"}}, +{ +// category name actor (function) params +// ------------- ----------------------- --------------------- ---------- + {"poolpair", "listpoolpairs", &listpoolpairs, {"pagination", "verbose"}}, + {"poolpair", "getpoolpair", &getpoolpair, {"key", "verbose" }}, + {"poolpair", "addpoolliquidity", &addpoolliquidity, {"from", "shareAddress", "inputs"}}, + {"poolpair", "removepoolliquidity", &removepoolliquidity, {"from", "amount", "inputs"}}, + {"poolpair", "createpoolpair", &createpoolpair, {"metadata", "inputs"}}, + {"poolpair", "updatepoolpair", &updatepoolpair, {"metadata", "inputs"}}, + {"poolpair", "poolswap", &poolswap, {"metadata", "inputs"}}, + {"poolpair", "compositeswap", &compositeswap, {"metadata", "inputs"}}, + {"poolpair", "listpoolshares", &listpoolshares, {"pagination", "verbose", "is_mine_only"}}, + {"poolpair", "testpoolswap", &testpoolswap, {"metadata", "path", "verbose"}}, }; void RegisterPoolpairRPCCommands(CRPCTable& tableRPC) { diff --git a/src/masternodes/rpc_vault.cpp b/src/masternodes/rpc_vault.cpp index c93b0d193a..9b82601392 100644 --- a/src/masternodes/rpc_vault.cpp +++ b/src/masternodes/rpc_vault.cpp @@ -875,7 +875,7 @@ UniValue placeauctionbid(const JSONRPCRequest& request) { { {"vaultId", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "Vault id"}, {"index", RPCArg::Type::NUM, RPCArg::Optional::NO, "Auction index"}, - {"from", RPCArg::Type::STR, RPCArg::Optional::NO, "Address to get tokens"}, + {"from", RPCArg::Type::STR, RPCArg::Optional::NO, "Address to get tokens. If \"from\" value is: \"*\" (star), it's means auto-selection accounts from wallet."}, {"amount", RPCArg::Type::STR, RPCArg::Optional::NO, "Amount of amount@symbol format"}, {"inputs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of json objects", { @@ -911,9 +911,28 @@ UniValue placeauctionbid(const JSONRPCRequest& request) { // decode vaultId CVaultId vaultId = ParseHashV(request.params[0], "vaultId"); uint32_t index = request.params[1].get_int(); - auto from = DecodeScript(request.params[2].get_str()); CTokenAmount amount = DecodeAmount(pwallet->chain(), request.params[3].get_str(), "amount"); + CScript from = {}; + auto fromStr = request.params[2].get_str(); + if (fromStr == "*") { + auto selectedAccounts = SelectAccountsByTargetBalances(GetAllMineAccounts(pwallet), CBalances{TAmounts{{amount.nTokenId, amount.nValue}}}, SelectionPie); + + for (auto& account : selectedAccounts) { + if (account.second.balances[amount.nTokenId] >= amount.nValue) { + from = account.first; + break; + } + } + + if (from.empty()) { + throw JSONRPCError(RPC_INVALID_REQUEST, + "Not enough tokens on account, call sendtokenstoaddress to increase it.\n"); + } + } else { + from = DecodeScript(fromStr); + } + CAuctionBidMessage msg{vaultId, index, from, amount}; CDataStream markedMetadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); markedMetadata << static_cast(CustomTxType::AuctionBid) diff --git a/src/miner.cpp b/src/miner.cpp index 5805757d5a..1aef5c8160 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -289,7 +289,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); - pblock->height = pindexPrev->nHeight + 1; + pblock->deprecatedHeight = pindexPrev->nHeight + 1; pblock->nBits = pos::GetNextWorkRequired(pindexPrev, pblock->nTime, consensus); if (myIDs) { pblock->stakeModifier = pos::ComputeStakeModifier(pindexPrev->stakeModifier, myIDs->first); @@ -719,7 +719,7 @@ namespace pos { tip = ::ChainActive().Tip(); masternodeID = *optMasternodeID; auto nodePtr = pcustomcsview->GetMasternode(masternodeID); - if (!nodePtr || !nodePtr->IsActive(tip->height + 1)) + if (!nodePtr || !nodePtr->IsActive(tip->nHeight + 1)) { /// @todo may be new status for not activated (or already resigned) MN?? return Status::initWaiting; @@ -727,7 +727,7 @@ namespace pos { mintedBlocks = nodePtr->mintedBlocks; if (args.coinbaseScript.empty()) { // this is safe cause MN was found - if (tip->height >= chainparams.GetConsensus().FortCanningHeight && nodePtr->rewardAddressType != 0) { + if (tip->nHeight >= chainparams.GetConsensus().FortCanningHeight && nodePtr->rewardAddressType != 0) { scriptPubKey = GetScriptForDestination(nodePtr->rewardAddressType == PKHashType ? CTxDestination(PKHash(nodePtr->rewardAddress)) : CTxDestination(WitnessV0KeyHash(nodePtr->rewardAddress)) @@ -743,7 +743,7 @@ namespace pos { scriptPubKey = args.coinbaseScript; } - blockHeight = tip->height + 1; + blockHeight = tip->nHeight + 1; creationHeight = int64_t(nodePtr->creationHeight); blockTime = std::max(tip->GetMedianTimePast() + 1, GetAdjustedTime()); timelock = pcustomcsview->GetTimelock(masternodeID, *nodePtr, blockHeight); @@ -841,7 +841,6 @@ namespace pos { auto pblock = std::make_shared(pblocktemplate->block); pblock->nBits = nBits; - pblock->height = blockHeight; pblock->mintedBlocks = mintedBlocks + 1; pblock->stakeModifier = std::move(stakeModifier); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 40a66eef90..59b1c324d4 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2655,7 +2655,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // possible optimization: stops when first quorum reached (irl, no need to walk deeper) auto topAnchor = panchors->GetActiveAnchor(); // limit requested by the top anchor, if any - if (topAnchor && topAnchor->anchor.height > pLowRequested->height && topAnchor->anchor.height <= (uint64_t) ::ChainActive().Height()) { + if (topAnchor && topAnchor->anchor.height > pLowRequested->nHeight && topAnchor->anchor.height <= (uint64_t) ::ChainActive().Height()) { pLowRequested = ::ChainActive()[topAnchor->anchor.height]; assert(pLowRequested); } @@ -2987,7 +2987,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr return true; } std::vector dummy; - status = tempBlock.FillBlock(*pblock, dummy); + status = tempBlock.FillBlock(*pblock, dummy, pindex->nHeight); if (status == READ_STATUS_OK) { fBlockReconstructed = true; } @@ -3079,7 +3079,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } PartiallyDownloadedBlock& partialBlock = *it->second.second->partialBlock; - ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn); + const auto prevIndex = LookupBlockIndex(partialBlock.header.hashPrevBlock); + if (!prevIndex) { + // Should never get here. Valid header including hashPrevBlock should be checked in CMPCTBLOCK. + return true; + } + ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn, prevIndex->nHeight + 1); if (status == READ_STATUS_INVALID) { MarkBlockAsReceived(resp.blockhash); // Reset in-flight state in case of whitelist Misbehaving(pfrom->GetId(), 100, strprintf("Peer %d sent us invalid compact block/non-matching block transactions\n", pfrom->GetId())); diff --git a/src/pos.cpp b/src/pos.cpp index 06acea6083..4b15bfcb94 100644 --- a/src/pos.cpp +++ b/src/pos.cpp @@ -31,7 +31,7 @@ bool CheckStakeModifier(const CBlockIndex* pindexPrev, const CBlockHeader& block /// Check PoS signatures (PoS block hashes are signed with coinstake out pubkey) bool CheckHeaderSignature(const CBlockHeader& blockHeader) { if (blockHeader.sig.empty()) { - if (blockHeader.height == 0) { + if (blockHeader.GetHash() == Params().GetConsensus().hashGenesisBlock) { return true; } LogPrintf("CheckBlockSignature: Bad Block - PoS signature is empty\n"); @@ -47,9 +47,9 @@ bool CheckHeaderSignature(const CBlockHeader& blockHeader) { return true; } -bool ContextualCheckProofOfStake(const CBlockHeader& blockHeader, const Consensus::Params& params, CCustomCSView* mnView, CheckContextState& ctxState) { +bool ContextualCheckProofOfStake(const CBlockHeader& blockHeader, const Consensus::Params& params, CCustomCSView* mnView, CheckContextState& ctxState, const int height) { /// @todo may be this is tooooo optimistic? need more validation? - if (blockHeader.height == 0 && blockHeader.GetHash() == params.hashGenesisBlock) { + if (height == 0 && blockHeader.GetHash() == params.hashGenesisBlock) { return true; } @@ -70,19 +70,19 @@ bool ContextualCheckProofOfStake(const CBlockHeader& blockHeader, const Consensu } masternodeID = *optMasternodeID; auto nodePtr = mnView->GetMasternode(masternodeID); - if (!nodePtr || !nodePtr->IsActive(blockHeader.height)) { + if (!nodePtr || !nodePtr->IsActive(height)) { return false; } creationHeight = int64_t(nodePtr->creationHeight); - if (blockHeader.height >= static_cast(params.EunosPayaHeight)) { - timelock = mnView->GetTimelock(masternodeID, *nodePtr, blockHeader.height); + if (height >= static_cast(params.EunosPayaHeight)) { + timelock = mnView->GetTimelock(masternodeID, *nodePtr, height); } // Check against EunosPayaHeight here for regtest, does not hurt other networks. // Redundant checks, but intentionally kept for easier fork accounting. - if (blockHeader.height >= static_cast(params.DakotaCrescentHeight) || blockHeader.height >= static_cast(params.EunosPayaHeight)) { - const auto usedHeight = blockHeader.height <= static_cast(params.EunosHeight) ? creationHeight : blockHeader.height; + if (height >= static_cast(params.DakotaCrescentHeight) || height >= static_cast(params.EunosPayaHeight)) { + const auto usedHeight = height <= static_cast(params.EunosHeight) ? creationHeight : height; // Get block times subNodesBlockTime = mnView->GetBlockTimes(nodePtr->operatorAuthAddress, usedHeight, creationHeight, timelock); @@ -90,7 +90,7 @@ bool ContextualCheckProofOfStake(const CBlockHeader& blockHeader, const Consensu } // checking PoS kernel is faster, so check it first - if (!CheckKernelHash(blockHeader.stakeModifier, blockHeader.nBits, creationHeight, blockHeader.GetBlockTime(),blockHeader.height, + if (!CheckKernelHash(blockHeader.stakeModifier, blockHeader.nBits, creationHeight, blockHeader.GetBlockTime(),height, masternodeID, params, subNodesBlockTime, timelock, ctxState)) { return false; } @@ -103,7 +103,7 @@ bool CheckProofOfStake(const CBlockHeader& blockHeader, const CBlockIndex* pinde // this is our own check of own minted block (just to remember) CheckContextState ctxState; - return CheckStakeModifier(pindexPrev, blockHeader) && ContextualCheckProofOfStake(blockHeader, params, mnView, ctxState); + return CheckStakeModifier(pindexPrev, blockHeader) && ContextualCheckProofOfStake(blockHeader, params, mnView, ctxState, pindexPrev->nHeight + 1); } unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nFirstBlockTime, const Consensus::Params::PoS& params, bool newDifficultyAdjust) diff --git a/src/pos.h b/src/pos.h index 0a4521bbdc..1df8ad8b3d 100644 --- a/src/pos.h +++ b/src/pos.h @@ -35,7 +35,7 @@ namespace pos { bool CheckHeaderSignature(const CBlockHeader& block); /// Check kernel hash target and coinstake signature - bool ContextualCheckProofOfStake(const CBlockHeader& blockHeader, const Consensus::Params& params, CCustomCSView* mnView, CheckContextState& ctxState); + bool ContextualCheckProofOfStake(const CBlockHeader& blockHeader, const Consensus::Params& params, CCustomCSView* mnView, CheckContextState& ctxState, const int height); /// Check kernel hash target and coinstake signature. Check that block coinstakeTx matches header bool CheckProofOfStake(const CBlockHeader& blockHeader, const CBlockIndex* pindexPrev, const Consensus::Params& params, CCustomCSView* mnView); diff --git a/src/primitives/block.cpp b/src/primitives/block.cpp index ff848c6e49..fa7826e1b3 100644 --- a/src/primitives/block.cpp +++ b/src/primitives/block.cpp @@ -19,7 +19,7 @@ uint256 CBlockHeader::GetHash() const uint256 CBlockHeader::GetHashToSign() const { CDataStream ss(SER_GETHASH, 0); - ss << nVersion << hashPrevBlock << hashMerkleRoot << nTime << nBits << height << mintedBlocks << stakeModifier; + ss << nVersion << hashPrevBlock << hashMerkleRoot << nTime << nBits << deprecatedHeight << mintedBlocks << stakeModifier; return Hash(ss.begin(), ss.end()); } @@ -32,7 +32,7 @@ std::string CBlock::ToString() const hashPrevBlock.ToString(), hashMerkleRoot.ToString(), nTime, nBits, - height, mintedBlocks, + deprecatedHeight, mintedBlocks, vtx.size()); for (const auto& tx : vtx) { s << " " << tx->ToString() << "\n"; diff --git a/src/primitives/block.h b/src/primitives/block.h index f3b50a0641..1342eb1617 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -31,7 +31,7 @@ class CBlockHeader uint32_t nTime; uint32_t nBits; - uint64_t height; + uint64_t deprecatedHeight; uint64_t mintedBlocks; uint256 stakeModifier; std::vector sig; @@ -51,7 +51,7 @@ class CBlockHeader READWRITE(nTime); READWRITE(nBits); READWRITE(stakeModifier); - READWRITE(height); + READWRITE(deprecatedHeight); READWRITE(mintedBlocks); READWRITE(sig); } @@ -64,7 +64,7 @@ class CBlockHeader nTime = 0; nBits = 0; stakeModifier.SetNull(); - height = 0; + deprecatedHeight = 0; mintedBlocks = 0; sig = {}; recoveredPubKey = CPubKey{}; @@ -142,7 +142,7 @@ class CBlock : public CBlockHeader block.nTime = nTime; block.nBits = nBits; block.stakeModifier = stakeModifier; - block.height = height; + block.deprecatedHeight = deprecatedHeight; block.mintedBlocks = mintedBlocks; block.sig = sig; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 73f03b8b52..cfe92cff0c 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1358,6 +1358,8 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) BuriedForkDescPushBack(softforks, "eunospaya", consensusParams.EunosPayaHeight); BuriedForkDescPushBack(softforks, "fortcanning", consensusParams.FortCanningHeight); BuriedForkDescPushBack(softforks, "fortcanningmuseum", consensusParams.FortCanningMuseumHeight); + BuriedForkDescPushBack(softforks, "fortcanningpark", consensusParams.FortCanningParkHeight); + BuriedForkDescPushBack(softforks, "fortcanninghill", consensusParams.FortCanningHillHeight); BuriedForkDescPushBack(softforks, "greatworld", consensusParams.GreatWorldHeight); BIP9SoftForkDescPushBack(softforks, "testdummy", consensusParams, Consensus::DEPLOYMENT_TESTDUMMY); obj.pushKV("softforks", softforks); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 7b4355fcc9..1b32d7d64c 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -280,6 +280,8 @@ static const CRPCConvertParam vRPCConvertParams[] = { "spv_listanchors", 1, "maxBtcHeight" }, { "spv_listanchors", 2, "minConfs" }, { "spv_listanchors", 3, "maxConfs" }, + { "spv_listanchors", 4, "startBtcHeight" }, + { "spv_listanchors", 5, "limit" }, { "spv_sendtoaddress", 1, "amount" }, { "spv_sendtoaddress", 2, "feerate" }, { "spv_listreceivedbyaddress", 0, "minconf" }, @@ -293,6 +295,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "compositeswap", 0, "metadata" }, { "compositeswap", 1, "inputs" }, { "testpoolswap", 0, "metadata"}, + { "testpoolswap", 2, "verbose"}, { "listpoolshares", 0, "pagination" }, { "listpoolshares", 1, "verbose" }, { "listpoolshares", 2, "is_mine_only" }, diff --git a/src/spv/spv_rpc.cpp b/src/spv/spv_rpc.cpp index a3ee578155..9554086146 100644 --- a/src/spv/spv_rpc.cpp +++ b/src/spv/spv_rpc.cpp @@ -432,6 +432,8 @@ UniValue spv_listanchors(const JSONRPCRequest& request) {"maxBtcHeight", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "max btc height, optional (default = -1)"}, {"minConfs", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "min anchor confirmations, optional (default = -1)"}, {"maxConfs", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "max anchor confirmations, optional (default = -1)"}, + {"startBtcHeight", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "max anchor confirmations, optional (default = -1)"}, + {"limit", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "number of records to return (default = unlimited)"}, }, RPCResult{ "\"array\" Returns array of anchors\n" @@ -445,13 +447,14 @@ UniValue spv_listanchors(const JSONRPCRequest& request) if (!spv::pspv) throw JSONRPCError(RPC_INVALID_REQUEST, "spv module disabled"); - RPCTypeCheck(request.params, { UniValue::VNUM, UniValue::VNUM, UniValue::VNUM, UniValue::VNUM }, true); + RPCTypeCheck(request.params, { UniValue::VNUM, UniValue::VNUM, UniValue::VNUM, UniValue::VNUM, UniValue::VNUM, UniValue::VNUM }, true); - - int minBtcHeight = request.params.size() > 0 && !request.params[0].isNull() ? request.params[0].get_int() : -1; - int maxBtcHeight = request.params.size() > 1 && !request.params[1].isNull() ? request.params[1].get_int() : -1; - int minConfs = request.params.size() > 2 && !request.params[2].isNull() ? request.params[2].get_int() : -1; - int maxConfs = request.params.size() > 3 && !request.params[3].isNull() ? request.params[3].get_int() : -1; + const int minBtcHeight = request.params.size() > 0 ? request.params[0].get_int() : -1; + const int maxBtcHeight = request.params.size() > 1 ? request.params[1].get_int() : -1; + const int minConfs = request.params.size() > 2 ? request.params[2].get_int() : -1; + const int maxConfs = request.params.size() > 3 ? request.params[3].get_int() : -1; + const int startBtcHeight = request.params.size() > 4 ? request.params[4].get_int() : -1; + const int limit = request.params.size() > 5 ? request.params[5].get_int() : std::numeric_limits::max(); // ! before cs_main lock uint32_t const tmp = spv::pspv->GetLastBlockHeight(); @@ -461,13 +464,16 @@ UniValue spv_listanchors(const JSONRPCRequest& request) panchors->UpdateLastHeight(tmp); // may be unnecessary but for sure auto const * cur = panchors->GetActiveAnchor(); + auto count = limit; UniValue result(UniValue::VARR); - panchors->ForEachAnchorByBtcHeight([&result, &cur, minBtcHeight, maxBtcHeight, minConfs, maxConfs](const CAnchorIndex::AnchorRec & rec) { + panchors->ForEachAnchorByBtcHeight([&](const CAnchorIndex::AnchorRec & rec) { // from tip to genesis: auto confs = panchors->GetAnchorConfirmations(&rec); - if ( (maxBtcHeight >= 0 && (int)rec.btcHeight > maxBtcHeight) || (minConfs >= 0 && confs < minConfs) ) + if ((maxBtcHeight >= 0 && (int)rec.btcHeight > maxBtcHeight) || (minConfs >= 0 && confs < minConfs)) return true; // continue - if ( (minBtcHeight >= 0 && (int)rec.btcHeight < minBtcHeight) || (maxConfs >= 0 && confs > maxConfs) ) + if ((minBtcHeight >= 0 && (int)rec.btcHeight < minBtcHeight) || + (maxConfs >= 0 && confs > maxConfs) || + (startBtcHeight >= 0 && static_cast(rec.btcHeight) < startBtcHeight)) return false; // break UniValue anchor(UniValue::VOBJ); @@ -480,7 +486,7 @@ UniValue spv_listanchors(const JSONRPCRequest& request) } result.push_back(anchor); - return true; + return --count != 0; }); return result; } @@ -1576,7 +1582,7 @@ static const CRPCCommand commands[] = { "spv", "spv_rescan", &spv_rescan, { "height" } }, { "spv", "spv_syncstatus", &spv_syncstatus, { } }, { "spv", "spv_gettxconfirmations", &spv_gettxconfirmations, { "txhash" } }, - { "spv", "spv_listanchors", &spv_listanchors, { "minBtcHeight", "maxBtcHeight", "minConfs", "maxConfs" } }, + { "spv", "spv_listanchors", &spv_listanchors, { "minBtcHeight", "maxBtcHeight", "minConfs", "maxConfs", "startBtcHeight", "limit" } }, { "spv", "spv_listanchorauths", &spv_listanchorauths, { } }, { "spv", "spv_listanchorrewardconfirms", &spv_listanchorrewardconfirms, { } }, { "spv", "spv_listanchorrewards", &spv_listanchorrewards, { } }, diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index f5ab2d6c4d..ff94e7fd17 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -52,14 +52,14 @@ static CBlock BuildBlockTestCase() { tip = ::ChainActive().Tip(); auto nodePtr = pcustomcsview->GetMasternode(masternodeID); - if (!nodePtr || !nodePtr->IsActive(tip->height)) + if (!nodePtr || !nodePtr->IsActive(tip->nHeight)) return {}; mintedBlocks = nodePtr->mintedBlocks; creationHeight = int64_t(nodePtr->creationHeight); } - block.height = tip->nHeight + 1; + block.deprecatedHeight = tip->nHeight + 1; block.mintedBlocks = mintedBlocks + 1; block.stakeModifier = pos::ComputeStakeModifier(tip->stakeModifier, minterKey.GetPubKey().GetID()); @@ -80,7 +80,7 @@ static CBlock BuildBlockTestCase() { block.nTime = 0; CheckContextState ctxState; - while (!pos::CheckKernelHash(block.stakeModifier, block.nBits, creationHeight, (int64_t) block.nTime, block.height, masternodeID, Params().GetConsensus(), {0, 0, 0, 0}, 0, ctxState)) block.nTime++; + while (!pos::CheckKernelHash(block.stakeModifier, block.nBits, creationHeight, (int64_t) block.nTime, block.deprecatedHeight, masternodeID, Params().GetConsensus(), {0, 0, 0, 0}, 0, ctxState)) block.nTime++; std::shared_ptr pblock = std::make_shared(std::move(block)); auto err = pos::SignPosBlock(pblock, minterKey); @@ -129,21 +129,21 @@ BOOST_AUTO_TEST_CASE(SimpleRoundTripTest) CBlock block2; { PartiallyDownloadedBlock tmp = partialBlock; - BOOST_CHECK(partialBlock.FillBlock(block2, {}) == READ_STATUS_INVALID); // No transactions + BOOST_CHECK(partialBlock.FillBlock(block2, {}, 0) == READ_STATUS_INVALID); // No transactions partialBlock = tmp; } // Wrong transaction { PartiallyDownloadedBlock tmp = partialBlock; - partialBlock.FillBlock(block2, {block.vtx[2]}); // Current implementation doesn't check txn here, but don't require that + partialBlock.FillBlock(block2, {block.vtx[2]}, 0); // Current implementation doesn't check txn here, but don't require that partialBlock = tmp; } bool mutated; BOOST_CHECK(block.hashMerkleRoot != BlockMerkleRoot(block2, &mutated)); CBlock block3; - BOOST_CHECK(partialBlock.FillBlock(block3, {block.vtx[1]}) == READ_STATUS_OK); + BOOST_CHECK(partialBlock.FillBlock(block3, {block.vtx[1]}, 0) == READ_STATUS_OK); BOOST_CHECK_EQUAL(block.GetHash().ToString(), block3.GetHash().ToString()); BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block3, &mutated).ToString()); BOOST_CHECK(!mutated); @@ -301,7 +301,7 @@ BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest) CBlock block2; PartiallyDownloadedBlock partialBlockCopy = partialBlock; - BOOST_CHECK(partialBlock.FillBlock(block2, {}) == READ_STATUS_OK); + BOOST_CHECK(partialBlock.FillBlock(block2, {}, 0) == READ_STATUS_OK); BOOST_CHECK_EQUAL(block.GetHash().ToString(), block2.GetHash().ToString()); bool mutated; BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block2, &mutated).ToString()); diff --git a/src/test/pos_tests.cpp b/src/test/pos_tests.cpp index 2885ab6571..4433da9aa1 100644 --- a/src/test/pos_tests.cpp +++ b/src/test/pos_tests.cpp @@ -26,7 +26,7 @@ std::shared_ptr Block( const uint256& prev_hash, const uint64_t& height, pblock->hashPrevBlock = prev_hash; pblock->mintedBlocks = mintedBlocks; - pblock->height = height; + pblock->deprecatedHeight = height; return pblock; } @@ -142,17 +142,17 @@ BOOST_AUTO_TEST_CASE(contextual_check_pos) CKey minterKey = pos->second.operatorKey; CheckContextState ctxState; - BOOST_CHECK(pos::ContextualCheckProofOfStake((CBlockHeader)Params().GenesisBlock(), Params().GetConsensus(), pcustomcsview.get(), ctxState)); + BOOST_CHECK(pos::ContextualCheckProofOfStake((CBlockHeader)Params().GenesisBlock(), Params().GetConsensus(), pcustomcsview.get(), ctxState, 0)); // uint256 prev_hash = uint256S("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"); uint64_t height = 0; uint64_t mintedBlocks = 1; std::shared_ptr block = Block(Params().GenesisBlock().GetHash(), height, mintedBlocks); - BOOST_CHECK(!pos::ContextualCheckProofOfStake(*(CBlockHeader*)block.get(), Params().GetConsensus(), pcustomcsview.get(), ctxState)); + BOOST_CHECK(!pos::ContextualCheckProofOfStake(*(CBlockHeader*)block.get(), Params().GetConsensus(), pcustomcsview.get(), ctxState, 0)); - block->height = 1; - BOOST_CHECK(!pos::ContextualCheckProofOfStake(*(CBlockHeader*)block.get(), Params().GetConsensus(), pcustomcsview.get(), ctxState)); + // Failure against a height of 1 + BOOST_CHECK(!pos::ContextualCheckProofOfStake(*(CBlockHeader*)block.get(), Params().GetConsensus(), pcustomcsview.get(), ctxState, 1)); } BOOST_AUTO_TEST_CASE(sign_pos_block) diff --git a/src/test/setup_common.cpp b/src/test/setup_common.cpp index a79f7acd6a..21807a4fb3 100644 --- a/src/test/setup_common.cpp +++ b/src/test/setup_common.cpp @@ -208,12 +208,12 @@ TestChain100Setup::CreateAndProcessBlock(const std::vector& tip = ::ChainActive().Tip(); auto nodePtr = pcustomcsview->GetMasternode(masternodeID); - if (!nodePtr || !nodePtr->IsActive(tip->height)) + if (!nodePtr || !nodePtr->IsActive(tip->nHeight)) throw std::runtime_error(std::string(__func__) + ": nodePtr does not exist"); mintedBlocks = nodePtr->mintedBlocks; } - block.height = tip->nHeight + 1; + block.deprecatedHeight = tip->nHeight + 1; block.mintedBlocks = mintedBlocks + 1; block.stakeModifier = pos::ComputeStakeModifier(tip->stakeModifier, minterKey.GetPubKey().GetID()); diff --git a/src/txdb.cpp b/src/txdb.cpp index e449384010..9aa12de804 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -273,7 +273,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, //PoS pindexNew->stakeModifier = diskindex.stakeModifier; - pindexNew->height = diskindex.height; + pindexNew->deprecatedHeight = diskindex.deprecatedHeight; pindexNew->mintedBlocks = diskindex.mintedBlocks; pindexNew->sig = diskindex.sig; if (pindexNew->nHeight && !skipSigCheck) { diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 9b8151eae5..2daefcd327 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -333,6 +333,7 @@ CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator) // accepting transactions becomes O(N^2) where N is the number // of transactions in the pool nCheckFrequency = 0; + accountsViewDirty = false; } bool CTxMemPool::isSpent(const COutPoint& outpoint) const @@ -570,59 +571,27 @@ void CTxMemPool::removeForBlock(const std::vector& vtx, unsigne { AssertLockHeld(cs); - setEntries staged; std::vector entries; for (const auto& tx : vtx) { auto it = mapTx.find(tx->GetHash()); if (it != mapTx.end()) { - staged.insert(it); entries.push_back(&*it); } } // Before the txs in the new block have been removed from the mempool, update policy estimates if (minerPolicyEstimator) {minerPolicyEstimator->processBlock(nBlockHeight, entries);} - for (auto& it : staged) { - auto& tx = it->GetTx(); - removeConflicts(tx); - ClearPrioritisation(tx.GetHash()); - } - - RemoveStaged(staged, true, MemPoolRemovalReason::BLOCK); - - if (pcustomcsview) { // can happen in tests - // check entire mempool - CAmount txfee = 0; - accountsView().Discard(); - CCustomCSView viewDuplicate(accountsView()); - CCoinsViewCache mempoolDuplicate(&::ChainstateActive().CoinsTip()); - - setEntries staged; - // Check custom TX consensus types are now not in conflict with account layer - auto& txsByEntryTime = mapTx.get(); - for (auto it = txsByEntryTime.begin(); it != txsByEntryTime.end(); ++it) { - CValidationState state; - const auto& tx = it->GetTx(); - if (!Consensus::CheckTxInputs(tx, state, mempoolDuplicate, &viewDuplicate, nBlockHeight, txfee, Params())) { - LogPrintf("%s: Remove conflicting TX: %s\n", __func__, tx.GetHash().GetHex()); - staged.insert(mapTx.project<0>(it)); - continue; - } - auto res = ApplyCustomTx(viewDuplicate, mempoolDuplicate, tx, Params().GetConsensus(), nBlockHeight); - if (!res.ok && (res.code & CustomTxErrCodes::Fatal)) { - LogPrintf("%s: Remove conflicting custom TX: %s\n", __func__, tx.GetHash().GetHex()); - staged.insert(mapTx.project<0>(it)); - } - } - - for (auto& it : staged) { - auto& tx = it->GetTx(); - removeConflicts(tx); - ClearPrioritisation(tx.GetHash()); + for (const auto& tx : vtx) { + auto it = mapTx.find(tx->GetHash()); + if (it != mapTx.end()) { + RemoveStaged({it}, true, MemPoolRemovalReason::BLOCK); } + removeConflicts(*tx); + ClearPrioritisation(tx->GetHash()); + } - RemoveStaged(staged, true, MemPoolRemovalReason::BLOCK); - viewDuplicate.Flush(); + if (pcustomcsview) { + rebuildAccountsView(nBlockHeight, &::ChainstateActive().CoinsTip()); } lastRollingFeeUpdate = GetTime(); @@ -975,27 +944,10 @@ size_t CTxMemPool::DynamicMemoryUsage() const { void CTxMemPool::RemoveStaged(const setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason) { AssertLockHeld(cs); UpdateForRemoveFromMempool(stage, updateDescendants); - std::set txids; for (txiter it : stage) { - txids.insert(it->GetTx().GetHash()); removeUnchecked(it, reason); } - if (pcustomcsview && !txids.empty()) { - auto& view = accountsView(); - std::map orderedTxs; - auto it = NewKVIterator(UndoKey{}, view.GetStorage().GetRaw()); - for (; it.Valid() && !txids.empty(); it.Next()) { - auto& key = it.Key(); - auto itTx = txids.find(key.txid); - if (itTx != txids.end()) { - orderedTxs.emplace(key.height, key.txid); - txids.erase(itTx); - } - } - for (auto it = orderedTxs.rbegin(); it != orderedTxs.rend(); ++it) { - view.OnUndoTx(it->second, it->first); - } - } + accountsViewDirty = accountsViewDirty || !stage.empty(); } int CTxMemPool::Expire(int64_t time) { @@ -1134,6 +1086,48 @@ void CTxMemPool::TrimToSize(size_t sizelimit, std::vector* pvNoSpends } } +void CTxMemPool::rebuildAccountsView(int height, const CCoinsViewCache& coinsCache) +{ + if (!pcustomcsview || !accountsViewDirty) { + return; + } + + CAmount txfee = 0; + accountsView().Discard(); + CCustomCSView viewDuplicate(accountsView()); + + setEntries staged; + std::vector vtx; + // Check custom TX consensus types are now not in conflict with account layer + auto& txsByEntryTime = mapTx.get(); + for (auto it = txsByEntryTime.begin(); it != txsByEntryTime.end(); ++it) { + CValidationState state; + const auto& tx = it->GetTx(); + if (!Consensus::CheckTxInputs(tx, state, coinsCache, &viewDuplicate, height, txfee, Params())) { + LogPrintf("%s: Remove conflicting TX: %s\n", __func__, tx.GetHash().GetHex()); + staged.insert(mapTx.project<0>(it)); + vtx.push_back(it->GetSharedTx()); + continue; + } + auto res = ApplyCustomTx(viewDuplicate, coinsCache, tx, Params().GetConsensus(), height); + if (!res && (res.code & CustomTxErrCodes::Fatal)) { + LogPrintf("%s: Remove conflicting custom TX: %s\n", __func__, tx.GetHash().GetHex()); + staged.insert(mapTx.project<0>(it)); + vtx.push_back(it->GetSharedTx()); + } + } + + RemoveStaged(staged, true, MemPoolRemovalReason::BLOCK); + + for (const auto& tx : vtx) { + removeConflicts(*tx); + ClearPrioritisation(tx->GetHash()); + } + + viewDuplicate.Flush(); + accountsViewDirty = false; +} + uint64_t CTxMemPool::CalculateDescendantMaximum(txiter entry) const { // find parent with highest descendant count std::vector candidates; diff --git a/src/txmempool.h b/src/txmempool.h index 41d5f0b143..0f5ca42f63 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -548,6 +548,7 @@ class CTxMemPool std::vector GetSortedDepthAndScore() const EXCLUSIVE_LOCKS_REQUIRED(cs); + bool accountsViewDirty; std::unique_ptr acview; public: indirectmap mapNextTx GUARDED_BY(cs); @@ -704,6 +705,7 @@ class CTxMemPool boost::signals2::signal NotifyEntryRemoved; CCustomCSView& accountsView(); + void rebuildAccountsView(int height, const CCoinsViewCache& coinsCache); private: /** UpdateForDescendants is used by UpdateTransactionsFromBlock to update * the descendants for a single transaction that has been added to the diff --git a/src/validation.cpp b/src/validation.cpp index 7a0de1ff16..6b65b817d2 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -611,7 +611,8 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool const auto height = GetSpendHeight(view); - // it does not need to check mempool anymore it has view there + // rebuild accounts view if dirty + pool.rebuildAccountsView(height, view); CAmount nFees = 0; if (!Consensus::CheckTxInputs(tx, state, view, &mnview, height, nFees, chainparams)) { @@ -909,6 +910,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // Store transaction in memory pool.addUnchecked(entry, setAncestors, validForFeeEstimation); + mnview.Flush(); // trim mempool and check if tx was trimmed if (!bypass_limits) { @@ -916,7 +918,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool if (!pool.exists(hash)) return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool full"); } - mnview.Flush(); } GetMainSignals().TransactionAddedToMempool(ptx); @@ -1699,7 +1700,7 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI std::vector metadata; if (IsAnchorRewardTxPlus(tx, metadata)) { - LogPrint(BCLog::ANCHORING, "%s: disconnecting finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), block.height); + LogPrint(BCLog::ANCHORING, "%s: disconnecting finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), pindex->nHeight); CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); CAnchorFinalizationMessagePlus finMsg; ss >> finMsg; @@ -1715,11 +1716,11 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI disconnectedAnchorConfirms.push_back(message); } - LogPrint(BCLog::ANCHORING, "%s: disconnected finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), block.height); + LogPrint(BCLog::ANCHORING, "%s: disconnected finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), pindex->nHeight); } else if (IsAnchorRewardTx(tx, metadata)) { - LogPrint(BCLog::ANCHORING, "%s: disconnecting finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), block.height); + LogPrint(BCLog::ANCHORING, "%s: disconnecting finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), pindex->nHeight); CDataStream ss(metadata, SER_NETWORK, PROTOCOL_VERSION); CAnchorFinalizationMessage finMsg; ss >> finMsg; @@ -1737,7 +1738,7 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI mnview.RemoveRewardForAnchor(finMsg.btcTxHash); mnview.EraseAnchorConfirmData(finMsg.btcTxHash); - LogPrint(BCLog::ANCHORING, "%s: disconnected finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), block.height); + LogPrint(BCLog::ANCHORING, "%s: disconnected finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), pindex->nHeight); } } @@ -2265,7 +2266,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl CheckContextState ctxState; - if (!CheckBlock(block, state, chainparams.GetConsensus(), ctxState, !fJustCheck, !fJustCheck)) { + if (!CheckBlock(block, state, chainparams.GetConsensus(), ctxState, !fJustCheck, pindex->nHeight, !fJustCheck)) { if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED) { // We don't write down blocks to disk if they may have been // corrupted, so this should be impossible unless we're having hardware @@ -2581,7 +2582,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl std::vector metadata; if (IsAnchorRewardTxPlus(tx, metadata)) { if (!fJustCheck) { - LogPrint(BCLog::ANCHORING, "%s: connecting finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), block.height); + LogPrint(BCLog::ANCHORING, "%s: connecting finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), pindex->nHeight); } ResVal res = ApplyAnchorRewardTxPlus(mnview, tx, pindex->nHeight, metadata, chainparams.GetConsensus()); if (!res.ok) { @@ -2591,11 +2592,11 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl } rewardedAnchors.push_back(*res.val); if (!fJustCheck) { - LogPrint(BCLog::ANCHORING, "%s: connected finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), block.height); + LogPrint(BCLog::ANCHORING, "%s: connected finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), pindex->nHeight); } } else if (IsAnchorRewardTx(tx, metadata)) { if (!fJustCheck) { - LogPrint(BCLog::ANCHORING, "%s: connecting finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), block.height); + LogPrint(BCLog::ANCHORING, "%s: connecting finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), pindex->nHeight); } ResVal res = ApplyAnchorRewardTx(mnview, tx, pindex->nHeight, pindex->pprev ? pindex->pprev->stakeModifier : uint256(), metadata, chainparams.GetConsensus()); if (!res.ok) { @@ -2605,7 +2606,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl } rewardedAnchors.push_back(*res.val); if (!fJustCheck) { - LogPrint(BCLog::ANCHORING, "%s: connected finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), block.height); + LogPrint(BCLog::ANCHORING, "%s: connected finalization tx: %s block: %d\n", __func__, tx.GetHash().GetHex(), pindex->nHeight); } } } @@ -2724,13 +2725,13 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl auto res = cache.SubCommunityBalance(CommunityAccountType::IncentiveFunding, distributed.first); if (!res.ok) { - LogPrintf("Pool rewards: can't update community balance: %s. Block %ld (%s)\n", res.msg, block.height, block.GetHash().ToString()); + LogPrintf("Pool rewards: can't update community balance: %s. Block %ld (%s)\n", res.msg, pindex->nHeight, block.GetHash().ToString()); } if (pindex->nHeight >= chainparams.GetConsensus().FortCanningHeight) { res = cache.SubCommunityBalance(CommunityAccountType::Loan, distributed.second); if (!res.ok) { - LogPrintf("Pool rewards: can't update community balance: %s. Block %ld (%s)\n", res.msg, block.height, block.GetHash().ToString()); + LogPrintf("Pool rewards: can't update community balance: %s. Block %ld (%s)\n", res.msg, pindex->nHeight, block.GetHash().ToString()); } } @@ -4519,7 +4520,7 @@ static bool FindUndoPos(CValidationState &state, int nFile, FlatFilePos &pos, un return true; } -bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, CheckContextState& ctxState, bool fCheckPOS, bool fCheckMerkleRoot) +bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, CheckContextState& ctxState, bool fCheckPOS, const int height, bool fCheckMerkleRoot) { // These are checks that are independent of context. @@ -4528,13 +4529,13 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P // Check that the header is valid (particularly PoW). This is mostly // redundant with the call in AcceptBlockHeader. - if (!fIsFakeNet && fCheckPOS && !pos::ContextualCheckProofOfStake(block, consensusParams, pcustomcsview.get(), ctxState)) + if (!fIsFakeNet && fCheckPOS && !pos::ContextualCheckProofOfStake(block, consensusParams, pcustomcsview.get(), ctxState, height)) return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "high-hash", "proof of stake failed"); // Check the merkle root. // block merkle root is delayed to ConnectBlock to ensure account changes - if (fCheckMerkleRoot && (block.height < consensusParams.EunosHeight - || block.height >= consensusParams.EunosKampungHeight)) { + if (fCheckMerkleRoot && (height < consensusParams.EunosHeight + || height >= consensusParams.EunosKampungHeight)) { bool mutated; uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated); if (block.hashMerkleRoot != hashMerkleRoot2) @@ -4566,8 +4567,8 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P TBytes dummy; for (unsigned int i = 1; i < block.vtx.size(); i++) { if (block.vtx[i]->IsCoinBase() && - !IsAnchorRewardTx(*block.vtx[i], dummy, block.height >= consensusParams.FortCanningHeight) && - !IsAnchorRewardTxPlus(*block.vtx[i], dummy, block.height >= consensusParams.FortCanningHeight)) + !IsAnchorRewardTx(*block.vtx[i], dummy, height >= consensusParams.FortCanningHeight) && + !IsAnchorRewardTxPlus(*block.vtx[i], dummy, height >= consensusParams.FortCanningHeight)) return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-multiple", "more than one coinbase"); } } @@ -4581,7 +4582,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), state.GetDebugMessage())); } - if (!fIsFakeNet && fCheckPOS && block.height >= consensusParams.FortCanningHeight) { + if (!fIsFakeNet && fCheckPOS && height >= consensusParams.FortCanningHeight) { CKeyID minter; // this is safe cause pos::ContextualCheckProofOfStake checked block.ExtractMinterKey(minter); @@ -4688,7 +4689,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta assert(pindexPrev != nullptr); const int nHeight = pindexPrev->nHeight + 1; - if (nHeight >= params.GetConsensus().FortCanningMuseumHeight && nHeight != block.height) { + if (nHeight >= params.GetConsensus().FortCanningMuseumHeight && nHeight != block.deprecatedHeight) { return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "incorrect-height", "incorrect height set in block header"); } @@ -4710,7 +4711,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "time-too-old", strprintf("block's timestamp is too early. Block time: %d Min time: %d", block.GetBlockTime(), pindexPrev->GetMedianTimePast())); // Check timestamp - if (Params().NetworkIDString() != CBaseChainParams::REGTEST && block.height >= static_cast(consensusParams.EunosPayaHeight)) { + if (Params().NetworkIDString() != CBaseChainParams::REGTEST && nHeight >= static_cast(consensusParams.EunosPayaHeight)) { if (block.GetBlockTime() > GetTime() + MAX_FUTURE_BLOCK_TIME_EUNOSPAYA) return state.Invalid(ValidationInvalidReason::BLOCK_TIME_FUTURE, false, REJECT_INVALID, "time-too-new", strprintf("block timestamp too far in the future. Block time: %d Max time: %d", block.GetBlockTime(), GetTime() + MAX_FUTURE_BLOCK_TIME_EUNOSPAYA)); } @@ -4718,7 +4719,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME) return state.Invalid(ValidationInvalidReason::BLOCK_TIME_FUTURE, false, REJECT_INVALID, "time-too-new", "block timestamp too far in the future"); - if (block.height >= static_cast(consensusParams.DakotaCrescentHeight)) { + if (nHeight >= static_cast(consensusParams.DakotaCrescentHeight)) { if (block.GetBlockTime() > GetTime() + MAX_FUTURE_BLOCK_TIME_DAKOTACRESCENT) return state.Invalid(ValidationInvalidReason::BLOCK_TIME_FUTURE, false, REJECT_INVALID, "time-too-new", strprintf("block timestamp too far in the future. Block time: %d Max time: %d", block.GetBlockTime(), GetTime() + MAX_FUTURE_BLOCK_TIME_DAKOTACRESCENT)); } @@ -5008,7 +5009,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, CVali } CheckContextState ctxState; - if (!CheckBlock(block, state, chainparams.GetConsensus(), ctxState, false) || // false cause we can check pos context only on ConnectBlock + if (!CheckBlock(block, state, chainparams.GetConsensus(), ctxState, false, pindex->nHeight) || // false cause we can check pos context only on ConnectBlock !ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) { assert(IsBlockReason(state.GetReason())); if (state.IsInvalid() && state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) { @@ -5055,7 +5056,7 @@ void ProcessAuthsIfTipChanged(CBlockIndex const * oldTip, CBlockIndex const * ti auto topAnchor = panchors->GetActiveAnchor(); CTeamView::CTeam team; int teamChange = tip->nHeight; - auto const teamDakota = pcustomcsview->GetAuthTeam(tip->height); + auto const teamDakota = pcustomcsview->GetAuthTeam(tip->nHeight); if (!teamDakota || teamDakota->empty()) { return; } @@ -5066,12 +5067,12 @@ void ProcessAuthsIfTipChanged(CBlockIndex const * oldTip, CBlockIndex const * ti uint64_t topAnchorHeight = topAnchor ? static_cast(topAnchor->anchor.height) : 0; // we have no need to ask for auths at all if we have topAnchor higher than current chain - if (tip->height <= topAnchorHeight) { + if (tip->nHeight <= topAnchorHeight) { return; } CBlockIndex const * pindexFork = ::ChainActive().FindFork(oldTip); - uint64_t forkHeight = pindexFork && (pindexFork->height >= (uint64_t)consensus.mn.anchoringFrequency) ? pindexFork->height - (uint64_t)consensus.mn.anchoringFrequency : 0; + uint64_t forkHeight = pindexFork && (pindexFork->nHeight >= (uint64_t)consensus.mn.anchoringFrequency) ? pindexFork->nHeight - (uint64_t)consensus.mn.anchoringFrequency : 0; // limit fork height - trim it by the top anchor, if any forkHeight = std::max(forkHeight, topAnchorHeight); pindexFork = ::ChainActive()[forkHeight]; @@ -5094,12 +5095,12 @@ void ProcessAuthsIfTipChanged(CBlockIndex const * oldTip, CBlockIndex const * ti for (CBlockIndex const * pindex = tip; pindex && pindex != pindexFork && teamChange >= 0; pindex = pindex->pprev, --teamChange) { // Only anchor by specified frequency - if (pindex->height % consensus.mn.anchoringFrequency != 0) { + if (pindex->nHeight % consensus.mn.anchoringFrequency != 0) { continue; } // Get start anchor height - int anchorHeight = static_cast(pindex->height) - consensus.mn.anchoringFrequency; + int anchorHeight = static_cast(pindex->nHeight) - consensus.mn.anchoringFrequency; // Get anchor block from specified time depth int64_t timeDepth = consensus.mn.anchoringTimeDepth; @@ -5108,7 +5109,7 @@ void ProcessAuthsIfTipChanged(CBlockIndex const * oldTip, CBlockIndex const * ti } // Select a block further back to avoid Anchor too new error. - if (pindex->height >= consensus.FortCanningHeight) { + if (pindex->nHeight >= consensus.FortCanningHeight) { timeDepth += consensus.mn.anchoringAdditionalTimeDepth; while (anchorHeight > 0 && ::ChainActive()[anchorHeight]->nTime + timeDepth > pindex->nTime) { --anchorHeight; @@ -5134,7 +5135,7 @@ void ProcessAuthsIfTipChanged(CBlockIndex const * oldTip, CBlockIndex const * ti size_t prefixLength{CKeyID().size() - spv::BtcAnchorMarker.size() - sizeof(uint64_t)}; std::vector hashPrefix{pindex->GetBlockHash().begin(), pindex->GetBlockHash().begin() + prefixLength}; teamDetailsVector.insert(teamDetailsVector.end(), spv::BtcAnchorMarker.begin(), spv::BtcAnchorMarker.end()); // 3 Bytes - uint64_t anchorCreationHeight = pindex->height; + uint64_t anchorCreationHeight = pindex->nHeight; teamDetailsVector.insert(teamDetailsVector.end(), reinterpret_cast(&anchorCreationHeight), reinterpret_cast(&anchorCreationHeight) + sizeof(uint64_t)); // 8 Bytes teamDetailsVector.insert(teamDetailsVector.end(), hashPrefix.begin(), hashPrefix.end()); // 9 Bytes @@ -5181,12 +5182,22 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptrhashPrevBlock); + if (!prevIndex) { + ret = false; + state.Invalid(ValidationInvalidReason::BLOCK_MISSING_PREV, error("%s: prev block not found", __func__), 0, "prev-blk-not-found"); + } + // Ensure that CheckBlock() passes before calling AcceptBlock, as // belt-and-suspenders. // reverts a011b9db38ce6d3d5c1b67c1e3bad9365b86f2ce // we can end up in isolation banning all other nodes CheckContextState ctxState; - bool ret = CheckBlock(*pblock, state, chainparams.GetConsensus(), ctxState, false); // false cause we can check pos context only on ConnectBlock + if (ret) { + ret = CheckBlock(*pblock, state, chainparams.GetConsensus(), ctxState, false, prevIndex->nHeight + 1); // false cause we can check pos context only on ConnectBlock + } if (ret) { // Store to disk ret = ::ChainstateActive().AcceptBlock(pblock, state, chainparams, &pindex, fForceProcessing, nullptr, fNewBlock); @@ -5247,7 +5258,7 @@ bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams, // NOTE: ContextualCheckProofOfStake is called by CheckBlock if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, FormatStateMessage(state)); - if (!CheckBlock(block, state, chainparams.GetConsensus(), ctxState, false, fCheckMerkleRoot)) + if (!CheckBlock(block, state, chainparams.GetConsensus(), ctxState, false, indexDummy.nHeight, fCheckMerkleRoot)) return error("%s: Consensus::CheckBlock: %s", __func__, FormatStateMessage(state)); if (!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindexPrev)) return error("%s: Consensus::ContextualCheckBlock: %s", __func__, FormatStateMessage(state)); @@ -5663,7 +5674,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); // check level 1: verify block validity - if (nCheckLevel >= 1 && !CheckBlock(block, state, chainparams.GetConsensus(), ctxState, false)) // false cause we can check pos context only on ConnectBlock + if (nCheckLevel >= 1 && !CheckBlock(block, state, chainparams.GetConsensus(), ctxState, false, pindex->nHeight)) // false cause we can check pos context only on ConnectBlock return error("%s: *** found bad block at %d, hash=%s (%s)\n", __func__, pindex->nHeight, pindex->GetBlockHash().ToString(), FormatStateMessage(state)); // check level 2: verify undo validity diff --git a/src/validation.h b/src/validation.h index 21c5617583..7483e0da6d 100644 --- a/src/validation.h +++ b/src/validation.h @@ -396,7 +396,7 @@ bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex); /** Functions for validating blocks and updating the block tree */ /** Context-independent validity checks */ -bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, CheckContextState& ctxState, bool fCheckPOS, bool fCheckMerkleRoot = true); +bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, CheckContextState& ctxState, bool fCheckPOS, const int height, bool fCheckMerkleRoot = true); /** Check a block is completely valid from start to finish (only works on top of our current best block) */ bool TestBlockValidity(CValidationState& state, const CChainParams& chainparams, const CBlock& block, CBlockIndex* pindexPrev, bool fCheckMerkleRoot = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main); diff --git a/test/functional/feature_loan.py b/test/functional/feature_loan.py index c2a1316e4c..3a43246754 100755 --- a/test/functional/feature_loan.py +++ b/test/functional/feature_loan.py @@ -19,8 +19,8 @@ def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1'], - ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1'] + ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-bayfrontgardensheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1'], + ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-bayfrontgardensheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1'] ] def run_test(self): @@ -105,6 +105,7 @@ def run_test(self): 'mintable': True, 'interest': 1}) self.nodes[0].generate(6) + # take loan self.nodes[0].takeloan({ 'vaultId': vaultId1, @@ -237,11 +238,56 @@ def run_test(self): assert_equal(vault1['state'], "active") assert_equal(accountBal, ['1600.00000000@DFI', '1600.00000000@BTC', '226.00000000@TSLA']) + self.nodes[0].deposittovault(vaultId1, account, '600@DFI') self.nodes[0].generate(1) vault1 = self.nodes[0].getvault(vaultId1) assert_equal(vault1['collateralAmounts'], ['600.00000000@DFI']) + # Trigger liquidation updating price in oracle + oracle1_prices = [{"currency": "USD", "tokenAmount": "2@TSLA"}] + oracle2_prices = [{"currency": "USD", "tokenAmount": "3@TSLA"}] + timestamp = calendar.timegm(time.gmtime()) + self.nodes[0].setoracledata(oracle_id1, timestamp, oracle1_prices) + self.nodes[0].setoracledata(oracle_id2, timestamp, oracle2_prices) + self.nodes[0].generate(36) + + # take loan + self.nodes[0].takeloan({ + 'vaultId': vaultId1, + 'amounts': "1000@TSLA" + }) + self.nodes[0].generate(1) + + # Reset price in oracle + oracle1_prices = [{"currency": "USD", "tokenAmount": "200@TSLA"}] + oracle2_prices = [{"currency": "USD", "tokenAmount": "300@TSLA"}] + timestamp = calendar.timegm(time.gmtime()) + self.nodes[0].setoracledata(oracle_id1, timestamp, oracle1_prices) + self.nodes[0].setoracledata(oracle_id2, timestamp, oracle2_prices) + self.nodes[0].generate(36) + + # Not enought tokens on account + try: + self.nodes[0].placeauctionbid(vaultId1, 0, "*", "1600@TSLA") + except JSONRPCException as e: + errorString = e.error['message'] + assert("Not enough tokens on account, call sendtokenstoaddress to increase it." in errorString) + + newAddress = self.nodes[0].getnewaddress("") + self.nodes[0].sendtokenstoaddress({}, {newAddress: "1600@TSLA"}) # newAddress has now the highest amount of TSLA token + self.nodes[0].generate(1) + + self.nodes[0].placeauctionbid(vaultId1, 0, "*", "1600@TSLA") # Autoselect address with highest amount of TSLA token + self.nodes[0].generate(40) # let auction end + + auction = self.nodes[0].listauctionhistory(newAddress)[0] + assert_equal(auction['winner'], newAddress) + assert_equal(auction['blockHeight'], 600) + assert_equal(auction['vaultId'], vaultId1) + assert_equal(auction['batchIndex'], 0) + assert_equal(auction['auctionBid'], "1600.00000000@TSLA") + if __name__ == '__main__': LoanTest().main() diff --git a/test/functional/feature_loan_basics.py b/test/functional/feature_loan_basics.py index 7b9752b39c..92d2f61ec0 100755 --- a/test/functional/feature_loan_basics.py +++ b/test/functional/feature_loan_basics.py @@ -19,8 +19,8 @@ def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-fortcanningheight=50', '-eunosheight=50', '-txindex=1'], - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-fortcanningheight=50', '-eunosheight=50', '-txindex=1']] + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-txindex=1'], + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=1', '-fortcanningheight=50', '-eunosheight=50', '-txindex=1']] def run_test(self): assert_equal(len(self.nodes[0].listtokens()), 1) # only one token == DFI @@ -370,7 +370,7 @@ def run_test(self): # loan payback burn vaultInfo = self.nodes[0].getvault(vaultId) - assert_equal(self.nodes[0].getburninfo()['paybackburn'], Decimal('0.00186824')) + assert_equal(self.nodes[0].getburninfo()['paybackburn'], Decimal('0.00186822')) assert_equal(sorted(vaultInfo['loanAmounts']), sorted(['0.50000057@' + symbolTSLA, '1.00000133@' + symbolGOOGL])) try: @@ -402,14 +402,13 @@ def run_test(self): 'from': account0, 'amounts': vaultInfo['loanAmounts']}) - self.nodes[0].generate(1) self.sync_blocks() vaultInfo = self.nodes[0].getvault(vaultId) assert_equal(vaultInfo['loanAmounts'], []) assert_equal(sorted(self.nodes[0].listaccounthistory(account0)[0]['amounts']), sorted(['-1.00001463@GOOGL', '-0.50000627@TSLA'])) - assert_greater_than_or_equal(self.nodes[0].getburninfo()['paybackburn'], Decimal('0.00443691')) + assert_greater_than_or_equal(self.nodes[0].getburninfo()['paybackburn'], Decimal('0.00443685')) for interest in self.nodes[0].getinterest('LOAN150'): if interest['token'] == symbolTSLA: @@ -458,5 +457,59 @@ def run_test(self): assert_equal(len(vaultInfo['batches']), 1) assert_equal(vaultInfo['batches'][0]['collaterals'], ['400.00000000@DFI']) + address = self.nodes[0].getnewaddress() + self.nodes[0].utxostoaccount({address: "100@" + symbolDFI}) + self.nodes[0].generate(1) + + vaultId3 = self.nodes[0].createvault(address) + self.nodes[0].generate(1) + + self.nodes[0].deposittovault(vaultId3, address, "100@DFI") + self.nodes[0].generate(1) + + # take loan + self.nodes[0].takeloan({ + 'vaultId': vaultId3, + 'amounts': ["10@TSLA", "10@GOOGL"] + }) + self.nodes[0].generate(1) + + address2 = self.nodes[0].getnewaddress() + self.nodes[0].sendtokenstoaddress({}, {address2:["5@TSLA", "5@GOOGL"]}) # split into two address + self.nodes[0].generate(1) + + try: + self.nodes[0].paybackloan({ + 'vaultId': vaultId3, + 'from': "*", + 'amounts': ["10@" + symbolTSLA, "10@" + symbolGOOGL] + }) + except JSONRPCException as e: + errorString = e.error['message'] + assert("Not enough tokens on account, call sendtokenstoaddress to increase it." in errorString) + + self.nodes[0].paybackloan({ + 'vaultId': vaultId3, + 'from': "*", + 'amounts': "5@" + symbolTSLA + }) + self.nodes[0].generate(1) + + vault = self.nodes[0].getvault(vaultId3) + assert_equal(sorted(vault['loanAmounts']), sorted(['5.00002853@' + symbolTSLA, '10.00003993@' + symbolGOOGL])) + + self.nodes[0].sendtokenstoaddress({}, {address2:["5@" + symbolTSLA, "10@" + symbolGOOGL]}) + self.nodes[0].generate(1) + + self.nodes[0].paybackloan({ + 'vaultId': vaultId3, + 'from': "*", + 'amounts': ["5@" + symbolTSLA, "10@" + symbolGOOGL] + }) + self.nodes[0].generate(1) + + vault = self.nodes[0].getvault(vaultId3) + assert_equal(sorted(vault['loanAmounts']), sorted(['0.00003425@' + symbolTSLA, '0.00005324@' + symbolGOOGL])) + if __name__ == '__main__': LoanTakeLoanTest().main() diff --git a/test/functional/feature_poolswap.py b/test/functional/feature_poolswap.py index 33827d65a6..d01d621386 100755 --- a/test/functional/feature_poolswap.py +++ b/test/functional/feature_poolswap.py @@ -183,12 +183,24 @@ def run_test(self): print("goldCheckPS:", goldCheckPS) print("testPoolSwapRes:", testPoolSwapRes) - testPoolSwapRes = str(testPoolSwapRes).split("@", 2) + testPoolSwapSplit = str(testPoolSwapRes).split("@", 2) - psTestAmount = testPoolSwapRes[0] - psTestTokenId = testPoolSwapRes[1] + psTestAmount = testPoolSwapSplit[0] + psTestTokenId = testPoolSwapSplit[1] assert_equal(psTestTokenId, idGold) + testPoolSwapVerbose = self.nodes[0].testpoolswap({ + "from": accountGN0, + "tokenFrom": symbolSILVER, + "amountFrom": 10, + "to": accountSN1, + "tokenTo": symbolGOLD, + }, "direct", True) + + assert_equal(testPoolSwapVerbose["path"], "direct") + assert_equal(testPoolSwapVerbose["pools"][0], idGS) + assert_equal(testPoolSwapVerbose["amount"], testPoolSwapRes) + self.nodes[0].poolswap({ "from": accountGN0, "tokenFrom": symbolSILVER, diff --git a/test/functional/feature_poolswap_composite.py b/test/functional/feature_poolswap_composite.py index 80655bbc2c..4a6ed6b90b 100755 --- a/test/functional/feature_poolswap_composite.py +++ b/test/functional/feature_poolswap_composite.py @@ -76,7 +76,6 @@ def run_test(self): idDOGE = list(self.nodes[0].gettoken(symbolDOGE).keys())[0] idTSLA = list(self.nodes[0].gettoken(symbolTSLA).keys())[0] idLTC = list(self.nodes[0].gettoken(symbolLTC).keys())[0] - coin = 100000000 # Creating poolpairs @@ -168,6 +167,7 @@ def run_test(self): }, collateral, []) self.nodes[0].generate(1) + self.nodes[0].compositeswap({ "from": source, "tokenFrom": symbolLTC, @@ -242,6 +242,34 @@ def run_test(self): }, collateral, []) self.nodes[0].generate(1) + estimateCompositePathsRes = self.nodes[0].testpoolswap({ + "from": source, + "tokenFrom": symbolLTC, + "amountFrom": ltc_to_doge_from, + "to": destination, + "tokenTo": symbolDOGE, + }, "auto", True) + + assert_equal(estimateCompositePathsRes['path'], 'auto') + + poolLTC_USDC = list(self.nodes[0].getpoolpair("LTC-USDC").keys())[0] + poolDOGE_USDC = list(self.nodes[0].getpoolpair("DOGE-USDC").keys())[0] + assert_equal(estimateCompositePathsRes['pools'], [poolLTC_USDC, poolDOGE_USDC]) + + testCPoolSwapRes = self.nodes[0].testpoolswap({ + "from": source, + "tokenFrom": symbolLTC, + "amountFrom": ltc_to_doge_from, + "to": destination, + "tokenTo": symbolDOGE, + }, "auto") + + testCPoolSwapRes = str(testCPoolSwapRes).split("@", 2) + + psTestAmount = testCPoolSwapRes[0] + psTestTokenId = testCPoolSwapRes[1] + assert_equal(psTestTokenId, idDOGE) + self.nodes[0].compositeswap({ "from": source, "tokenFrom": symbolLTC, @@ -261,6 +289,8 @@ def run_test(self): dest_balance = self.nodes[0].getaccount(destination, {}, True) assert_equal(dest_balance[idDOGE], doge_received * 2) assert_equal(len(dest_balance), 1) + # Check test swap correctness + assert_equal(Decimal(psTestAmount), dest_balance[idDOGE]) # Set up addresses for swapping source = self.nodes[0].getnewaddress("", "legacy") diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 0b527587c9..b835d88b45 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -133,6 +133,8 @@ def _test_getblockchaininfo(self): 'eunospaya': {'type': 'buried', 'active': False, 'height': 10000000}, 'fortcanning': {'type': 'buried', 'active': False, 'height': 10000000}, 'fortcanningmuseum': {'type': 'buried', 'active': False, 'height': 10000000}, + 'fortcanningpark': {'type': 'buried', 'active': False, 'height': 10000000}, + 'fortcanninghill': {'type': 'buried', 'active': False, 'height': 10000000}, 'greatworld': {'type': 'buried', 'active': False, 'height': 10000000}, 'testdummy': { 'type': 'bip9', diff --git a/test/functional/rpc_mn_basic.py b/test/functional/rpc_mn_basic.py old mode 100755 new mode 100644 index b5e86d726d..326f56ee18 --- a/test/functional/rpc_mn_basic.py +++ b/test/functional/rpc_mn_basic.py @@ -148,9 +148,11 @@ def run_test(self): self.nodes[2].generate(35) connect_nodes_bi(self.nodes, 0, 2) self.sync_blocks(self.nodes[0:3]) + assert_equal(len(self.nodes[0].listmasternodes()), 8) - # fundingTx is removed for a block - assert_equal(len(self.nodes[0].getrawmempool()), 1) # auto auth + mempool = self.nodes[0].getrawmempool() + assert(idnode0 in mempool and fundingTx in mempool) + assert_equal(len(mempool), 3) # + auto auth collateral0 = self.nodes[0].getnewaddress("", "legacy") self.nodes[0].createmasternode(collateral0) @@ -205,6 +207,9 @@ def run_test(self): # test getmasternodeblocks self.nodes[0].generate(1) node0_keys = self.nodes[0].get_genesis_keys() + blocks = self.nodes[0].getmasternodeblocks({'operatorAddress': node0_keys.operatorAuthAddress}, 2) + assert_equal(len(blocks), 2) + blocks = self.nodes[0].getmasternodeblocks({'operatorAddress': node0_keys.operatorAuthAddress}) assert_equal(list(blocks.keys())[0], '162')