diff --git a/doc/release-notes-6720.md b/doc/release-notes-6720.md new file mode 100644 index 000000000000..69936aeb26ca --- /dev/null +++ b/doc/release-notes-6720.md @@ -0,0 +1,4 @@ +Updated RPCs +------------ + +* A new optional field `submit` has been introduced to the `protx revoke`, `protx update_registrar`, `protx update_service` RPCs. It behaves identically to `submit` in `protx register` or `protx register_fund`. diff --git a/src/evo/creditpool.cpp b/src/evo/creditpool.cpp index 54a0c194bab9..ea5adf412f63 100644 --- a/src/evo/creditpool.cpp +++ b/src/evo/creditpool.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -47,29 +46,63 @@ static bool GetDataFromUnlockTx(const CTransaction& tx, CAmount& toUnlock, uint6 } namespace { - struct UnlockDataPerBlock { - CAmount unlocked{0}; - std::unordered_set indexes; - }; +struct CreditPoolDataPerBlock { + CAmount credit_pool{0}; + CAmount unlocked{0}; + std::unordered_set indexes; +}; } // anonymous namespace // it throws exception if anything went wrong -static UnlockDataPerBlock GetDataFromUnlockTxes(const std::vector& vtx) +static std::optional GetCreditDataFromBlock(const gsl::not_null block_index, + const Consensus::Params& consensusParams) { - UnlockDataPerBlock blockData; + // There's no CbTx before DIP0003 activation + if (!DeploymentActiveAt(*block_index, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) { + return std::nullopt; + } + + CreditPoolDataPerBlock blockData; + + static Mutex cache_mutex; + static unordered_lru_cache block_data_cache GUARDED_BY( + cache_mutex){static_cast(Params().CreditPoolPeriodBlocks()) * 2}; + if (LOCK(cache_mutex); block_data_cache.get(block_index->GetBlockHash(), blockData)) { + return blockData; + } + + CBlock block; + if (!ReadBlockFromDisk(block, block_index, consensusParams)) { + throw std::runtime_error("failed-getcbforblock-read"); + } - for (CTransactionRef tx : vtx) { + if (block.vtx.empty() || block.vtx[0]->vExtraPayload.empty() || !block.vtx[0]->IsSpecialTxVersion()) { + LogPrintf("%s: ERROR: empty CbTx for CreditPool at height=%d\n", __func__, block_index->nHeight); + return std::nullopt; + } + + + if (const auto opt_cbTx = GetTxPayload(block.vtx[0]->vExtraPayload); opt_cbTx) { + blockData.credit_pool = opt_cbTx->creditPoolBalance; + } else { + LogPrintf("%s: WARNING: No valid CbTx at height=%d\n", __func__, block_index->nHeight); + return std::nullopt; + } + for (CTransactionRef tx : block.vtx) { if (!tx->IsSpecialTxVersion() || tx->nType != TRANSACTION_ASSET_UNLOCK) continue; CAmount unlocked{0}; TxValidationState tx_state; uint64_t index{0}; if (!GetDataFromUnlockTx(*tx, unlocked, index, tx_state)) { - throw std::runtime_error(strprintf("%s: CCreditPoolManager::GetDataFromUnlockTxes failed: %s", __func__, tx_state.ToString())); + throw std::runtime_error(strprintf("%s: GetDataFromUnlockTx failed: %s", __func__, tx_state.ToString())); } blockData.unlocked += unlocked; blockData.indexes.insert(index); } + + LOCK(cache_mutex); + block_data_cache.insert(block_index->GetBlockHash(), blockData); return blockData; } @@ -112,32 +145,11 @@ void CCreditPoolManager::AddToCache(const uint256& block_hash, int height, const } } -static std::optional GetBlockForCreditPool(const gsl::not_null block_index, - const Consensus::Params& consensusParams) -{ - // There's no CbTx before DIP0003 activation - if (!DeploymentActiveAt(*block_index, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) { - return std::nullopt; - } - - CBlock block; - if (!ReadBlockFromDisk(block, block_index, consensusParams)) { - throw std::runtime_error("failed-getcbforblock-read"); - } - - if (block.vtx.empty() || block.vtx[0]->vExtraPayload.empty() || !block.vtx[0]->IsSpecialTxVersion()) { - LogPrintf("%s: ERROR: empty CbTx for CreditPool at height=%d\n", __func__, block_index->nHeight); - return std::nullopt; - } - - return block; -} - CCreditPool CCreditPoolManager::ConstructCreditPool(const gsl::not_null block_index, CCreditPool prev, const Consensus::Params& consensusParams) { - std::optional block = GetBlockForCreditPool(block_index, consensusParams); - if (!block) { + std::optional opt_block_data = GetCreditDataFromBlock(block_index, consensusParams); + if (!opt_block_data) { // If reading of previous block is not successfully, but // prev contains credit pool related data, something strange happened if (prev.locked != 0) { @@ -151,55 +163,46 @@ CCreditPool CCreditPoolManager::ConstructCreditPool(const gsl::not_nullGetBlockHash(), block_index->nHeight, emptyPool); return emptyPool; } - CAmount locked = [&, func=__func__]() { - const auto opt_cbTx = GetTxPayload(block->vtx[0]->vExtraPayload); - if (!opt_cbTx) { - throw std::runtime_error(strprintf("%s: failed-getcreditpool-cbtx-payload", func)); - } - return opt_cbTx->creditPoolBalance; - }(); + const CreditPoolDataPerBlock& blockData{*opt_block_data}; // We use here sliding window with Params().CreditPoolPeriodBlocks to determine // current limits for asset unlock transactions. // Indexes should not be duplicated since genesis block, but the Unlock Amount // of withdrawal transaction is limited only by this window - UnlockDataPerBlock blockData = GetDataFromUnlockTxes(block->vtx); CRangesSet indexes{std::move(prev.indexes)}; if (std::any_of(blockData.indexes.begin(), blockData.indexes.end(), [&](const uint64_t index) { return !indexes.Add(index); })) { throw std::runtime_error(strprintf("%s: failed-getcreditpool-index-duplicated", __func__)); } - const CBlockIndex* distant_block_index = block_index; - for ([[maybe_unused]] auto _ : irange::range(Params().CreditPoolPeriodBlocks())) { - distant_block_index = distant_block_index->pprev; - if (distant_block_index == nullptr) break; - } + const CBlockIndex* distant_block_index{ + block_index->GetAncestor(block_index->nHeight - Params().CreditPoolPeriodBlocks())}; CAmount distantUnlocked{0}; if (distant_block_index) { - if (std::optional distant_block = GetBlockForCreditPool(distant_block_index, consensusParams); distant_block) { - distantUnlocked = GetDataFromUnlockTxes(distant_block->vtx).unlocked; + if (std::optional distant_block{GetCreditDataFromBlock(distant_block_index, consensusParams)}; + distant_block) { + distantUnlocked = distant_block->unlocked; } } - CAmount currentLimit = locked; + CAmount currentLimit = blockData.credit_pool; const CAmount latelyUnlocked = prev.latelyUnlocked + blockData.unlocked - distantUnlocked; if (DeploymentActiveAt(*block_index, Params().GetConsensus(), Consensus::DEPLOYMENT_WITHDRAWALS)) { currentLimit = std::min(currentLimit, LimitAmountV22); } else { // Unlock limits in pre-v22 are max(100, min(.10 * assetlockpool, 1000)) inside window if (currentLimit + latelyUnlocked > LimitAmountLow) { - currentLimit = std::max(LimitAmountLow, locked / 10) - latelyUnlocked; + currentLimit = std::max(LimitAmountLow, blockData.credit_pool / 10) - latelyUnlocked; if (currentLimit < 0) currentLimit = 0; } currentLimit = std::min(currentLimit, LimitAmountHigh - latelyUnlocked); } - if (currentLimit != 0 || latelyUnlocked > 0 || locked > 0) { + if (currentLimit != 0 || latelyUnlocked > 0 || blockData.credit_pool > 0) { LogPrint(BCLog::CREDITPOOL, /* Continued */ "CCreditPoolManager: asset unlock limits on height: %d locked: %d.%08d limit: %d.%08d " "unlocked-in-window: %d.%08d\n", - block_index->nHeight, locked / COIN, locked % COIN, currentLimit / COIN, currentLimit % COIN, - latelyUnlocked / COIN, latelyUnlocked % COIN); + block_index->nHeight, blockData.credit_pool / COIN, blockData.credit_pool % COIN, currentLimit / COIN, + currentLimit % COIN, latelyUnlocked / COIN, latelyUnlocked % COIN); } if (currentLimit < 0) { @@ -207,7 +210,7 @@ CCreditPool CCreditPoolManager::ConstructCreditPool(const gsl::not_nullGetBlockHash(), block_index->nHeight, pool); return pool; diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index 0e451dd3779a..bd197c04e41c 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -320,7 +320,7 @@ static void SignSpecialTxPayloadByHash(const CMutableTransaction& tx, SpecialTxP payload.sig = key.Sign(hash, use_legacy); } -static std::string SignAndSendSpecialTx(const JSONRPCRequest& request, CChainstateHelper& chain_helper, const ChainstateManager& chainman, const CMutableTransaction& tx, bool fSubmit = true) +static std::string SignAndSendSpecialTx(const JSONRPCRequest& request, CChainstateHelper& chain_helper, const ChainstateManager& chainman, const CMutableTransaction& tx, bool fSubmit) { { LOCK(cs_main); @@ -551,9 +551,9 @@ static RPCHelpMan protx_register_fund_evo() }, { RPCResult{"if \"submit\" is not set or set to true", - RPCResult::Type::STR_HEX, "txid", "The transaction id"}, + RPCResult::Type::STR_HEX, "txid", "The transaction id"}, RPCResult{"if \"submit\" is set to false", - RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"}, + RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"}, }, RPCExamples{ HelpExampleCli("protx", "register_fund_evo \"" + EXAMPLE_ADDRESS[0] + "\" \"1.2.3.4:1234\" \"" + EXAMPLE_ADDRESS[1] + "\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"" + EXAMPLE_ADDRESS[1] + "\" 0 \"" + EXAMPLE_ADDRESS[0] + "\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")}, @@ -590,9 +590,9 @@ static RPCHelpMan protx_register_evo() }, { RPCResult{"if \"submit\" is not set or set to true", - RPCResult::Type::STR_HEX, "txid", "The transaction id"}, + RPCResult::Type::STR_HEX, "txid", "The transaction id"}, RPCResult{"if \"submit\" is set to false", - RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"}, + RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"}, }, RPCExamples{ HelpExampleCli("protx", "register_evo \"0123456701234567012345670123456701234567012345670123456701234567\" 0 \"1.2.3.4:1234\" \"" + EXAMPLE_ADDRESS[1] + "\" \"93746e8731c57f87f79b3620a7982924e2931717d49540a85864bd543de11c43fb868fd63e501a1db37e19ed59ae6db4\" \"" + EXAMPLE_ADDRESS[1] + "\" 0 \"" + EXAMPLE_ADDRESS[0] + "\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")}, @@ -897,7 +897,7 @@ static RPCHelpMan protx_register_submit() ptx.vchSig = opt_vchSig.value(); SetTxPayload(tx, ptx); - return SignAndSendSpecialTx(request, chain_helper, chainman, tx); + return SignAndSendSpecialTx(request, chain_helper, chainman, tx, /*fSubmit=*/true); }, }; } @@ -915,9 +915,13 @@ static RPCHelpMan protx_update_service() GetRpcArg("operatorKey"), GetRpcArg("operatorPayoutAddress"), GetRpcArg("feeSourceAddress"), + GetRpcArg("submit"), }, - RPCResult{ - RPCResult::Type::STR_HEX, "txid", "The transaction id" + { + RPCResult{"if \"submit\" is not set or set to true", + RPCResult::Type::STR_HEX, "txid", "The transaction id"}, + RPCResult{"if \"submit\" is set to false", + RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"}, }, RPCExamples{ HelpExampleCli("protx", "update_service \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" 5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd") @@ -947,9 +951,14 @@ static RPCHelpMan protx_update_service_evo() GetRpcArg("platformHTTPPort"), GetRpcArg("operatorPayoutAddress"), GetRpcArg("feeSourceAddress"), + GetRpcArg("submit"), + }, + { + RPCResult{"if \"submit\" is not set or set to true", + RPCResult::Type::STR_HEX, "txid", "The transaction id"}, + RPCResult{"if \"submit\" is set to false", + RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"}, }, - RPCResult{ - RPCResult::Type::STR_HEX, "txid", "The transaction id"}, RPCExamples{ HelpExampleCli("protx", "update_service_evo \"0123456701234567012345670123456701234567012345670123456701234567\" \"1.2.3.4:1234\" \"5a2e15982e62f1e0b7cf9783c64cf7e3af3f90a52d6c40f6f95d624c0b1621cd\" \"f2dbd9b0a1f541a7c44d34a58674d0262f5feca5\" 22821 22822")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue @@ -1055,6 +1064,11 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques } } + bool fSubmit{true}; + if (!request.params[paramIdx + 2].isNull()) { + fSubmit = ParseBoolV(request.params[paramIdx + 2], "submit"); + } + FundSpecialTx(*wallet, tx, ptx, feeSource); const bool isV19active = DeploymentActiveAfter(WITH_LOCK(cs_main, return chainman.ActiveChain().Tip();), @@ -1062,7 +1076,7 @@ static UniValue protx_update_service_common_wrapper(const JSONRPCRequest& reques SignSpecialTxPayloadByHash(tx, ptx, keyOperator, !isV19active); SetTxPayload(tx, ptx); - return SignAndSendSpecialTx(request, chain_helper, chainman, tx); + return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit); } static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_scheme) @@ -1083,9 +1097,13 @@ static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_ GetRpcArg("votingAddress_update"), GetRpcArg("payoutAddress_update"), GetRpcArg("feeSourceAddress"), + GetRpcArg("submit"), }, - RPCResult{ - RPCResult::Type::STR_HEX, "txid", "The transaction id" + { + RPCResult{"if \"submit\" is not set or set to true", + RPCResult::Type::STR_HEX, "txid", "The transaction id"}, + RPCResult{"if \"submit\" is set to false", + RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"}, }, RPCExamples{ HelpExampleCli("protx", rpc_example) @@ -1166,11 +1184,16 @@ static RPCHelpMan protx_update_registrar_wrapper(const bool specific_legacy_bls_ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Dash address: ") + request.params[4].get_str()); } + bool fSubmit{true}; + if (!request.params[5].isNull()) { + fSubmit = ParseBoolV(request.params[5], "submit"); + } + FundSpecialTx(*wallet, tx, ptx, feeSourceDest); SignSpecialTxPayloadByHash(tx, ptx, dmn->pdmnState->keyIDOwner, *wallet); SetTxPayload(tx, ptx); - return SignAndSendSpecialTx(request, chain_helper, chainman, tx); + return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit); }, }; } @@ -1198,9 +1221,13 @@ static RPCHelpMan protx_revoke() GetRpcArg("operatorKey"), GetRpcArg("reason"), GetRpcArg("feeSourceAddress"), + GetRpcArg("submit"), }, - RPCResult{ - RPCResult::Type::STR_HEX, "txid", "The transaction id" + { + RPCResult{"if \"submit\" is not set or set to true", + RPCResult::Type::STR_HEX, "txid", "The transaction id"}, + RPCResult{"if \"submit\" is set to false", + RPCResult::Type::STR_HEX, "hex", "The serialized signed ProTx in hex format"}, }, RPCExamples{ HelpExampleCli("protx", "revoke \"0123456701234567012345670123456701234567012345670123456701234567\" \"072f36a77261cdd5d64c32d97bac417540eddca1d5612f416feb07ff75a8e240\"") @@ -1265,10 +1292,15 @@ static RPCHelpMan protx_revoke() throw JSONRPCError(RPC_INTERNAL_ERROR, "No payout or fee source addresses found, can't revoke"); } + bool fSubmit{true}; + if (!request.params[4].isNull()) { + fSubmit = ParseBoolV(request.params[4], "submit"); + } + SignSpecialTxPayloadByHash(tx, ptx, keyOperator, !isV19active); SetTxPayload(tx, ptx); - return SignAndSendSpecialTx(request, chain_helper, chainman, tx); + return SignAndSendSpecialTx(request, chain_helper, chainman, tx, fSubmit); }, }; } diff --git a/test/functional/feature_dip3_deterministicmns.py b/test/functional/feature_dip3_deterministicmns.py index 5e012624ea32..3684033a8f45 100755 --- a/test/functional/feature_dip3_deterministicmns.py +++ b/test/functional/feature_dip3_deterministicmns.py @@ -213,7 +213,7 @@ def run_test(self): assert old_voting_address != new_voting_address # also check if funds from payout address are used when no fee source address is specified node.sendtoaddress(mn.rewards_address, 0.001) - node.protx('update_registrar' if softfork_active(node, 'v19') else 'update_registrar_legacy', mn.proTxHash, "", new_voting_address, "") + mn.update_registrar(node, submit=True, pubKeyOperator="", votingAddr=new_voting_address, rewards_address="") self.generate(node, 1) new_dmnState = mn.get_node(self).masternode("status")["dmnState"] new_voting_address_from_rpc = new_dmnState["votingAddress"] @@ -237,14 +237,15 @@ def create_mn_collateral(self, node, mn: MasternodeInfo): # register a protx MN and also fund it (using collateral inside ProRegTx) def register_fund_mn(self, node, mn: MasternodeInfo): node.sendtoaddress(mn.fundsAddr, mn.get_collateral_value() + 0.001) - txid = node.protx('register_fund' if softfork_active(node, 'v19') else 'register_fund_legacy', mn.collateral_address, '127.0.0.1:%d' % mn.nodePort, mn.ownerAddr, mn.pubKeyOperator, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr) + txid = mn.register_fund(node, submit=True) + assert txid is not None vout = mn.get_collateral_vout(node, txid) mn.set_params(proTxHash=txid, collateral_txid=txid, collateral_vout=vout) # create a protx MN which refers to an existing collateral def register_mn(self, node, mn: MasternodeInfo): node.sendtoaddress(mn.fundsAddr, 0.001) - proTxHash = node.protx('register' if softfork_active(node, 'v19') else 'register_legacy', mn.collateral_txid, mn.collateral_vout, '127.0.0.1:%d' % mn.nodePort, mn.ownerAddr, mn.pubKeyOperator, mn.votingAddr, mn.operator_reward, mn.rewards_address, mn.fundsAddr) + proTxHash = mn.register(node, submit=True) mn.set_params(proTxHash=proTxHash) self.generate(node, 1, sync_fun=self.no_op) @@ -263,14 +264,14 @@ def spend_mn_collateral(self, mn: MasternodeInfo, with_dummy_input_output=False) def update_mn_payee(self, mn: MasternodeInfo, payee): self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001) - self.nodes[0].protx('update_registrar' if softfork_active(self.nodes[0], 'v19') else 'update_registrar_legacy', mn.proTxHash, '', '', payee, mn.fundsAddr) + mn.update_registrar(self.nodes[0], submit=True, pubKeyOperator="", votingAddr="", rewards_address=payee, fundsAddr=mn.fundsAddr) self.generate(self.nodes[0], 1) info = self.nodes[0].protx('info', mn.proTxHash) assert info['state']['payoutAddress'] == payee def test_protx_update_service(self, mn: MasternodeInfo): self.nodes[0].sendtoaddress(mn.fundsAddr, 0.001) - self.nodes[0].protx('update_service', mn.proTxHash, '127.0.0.2:%d' % mn.nodePort, mn.keyOperator, "", mn.fundsAddr) + mn.update_service(self.nodes[0], submit=True, ipAndPort=f'127.0.0.2:{mn.nodePort}') self.generate(self.nodes[0], 1) for node in self.nodes: protx_info = node.protx('info', mn.proTxHash) @@ -279,7 +280,7 @@ def test_protx_update_service(self, mn: MasternodeInfo): assert_equal(mn_list['%s-%d' % (mn.collateral_txid, mn.collateral_vout)]['address'], '127.0.0.2:%d' % mn.nodePort) # undo - self.nodes[0].protx('update_service', mn.proTxHash, '127.0.0.1:%d' % mn.nodePort, mn.keyOperator, "", mn.fundsAddr) + mn.update_service(self.nodes[0], submit=True) self.generate(self.nodes[0], 1, sync_fun=self.no_op) def assert_mnlists(self, mns): diff --git a/test/functional/feature_dip3_v19.py b/test/functional/feature_dip3_v19.py index 35fc82ded75a..c72c4dc314e6 100755 --- a/test/functional/feature_dip3_v19.py +++ b/test/functional/feature_dip3_v19.py @@ -95,10 +95,8 @@ def run_test(self): assert evo_info_3 is not None self.dynamically_evo_update_service(evo_info_0, 9, should_be_rejected=True) - revoke_protx = self.mninfo[-1].proTxHash - revoke_keyoperator = self.mninfo[-1].keyOperator - self.log.info(f"Trying to revoke proTx:{revoke_protx}") - self.test_revoke_protx(evo_info_3.nodeIdx, revoke_protx, revoke_keyoperator) + self.log.info(f"Trying to revoke proTx:{self.mninfo[-1].proTxHash}") + self.test_revoke_protx(evo_info_3.nodeIdx, self.mninfo[-1]) self.mine_quorum(llmq_type_name='llmq_test', llmq_type=100) @@ -116,14 +114,14 @@ def run_test(self): self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) - def test_revoke_protx(self, node_idx, revoke_protx, revoke_keyoperator): + def test_revoke_protx(self, node_idx, revoke_mn: MasternodeInfo): funds_address = self.nodes[0].getnewaddress() fund_txid = self.nodes[0].sendtoaddress(funds_address, 1) self.bump_mocktime(10 * 60 + 1) # to make tx safe to include in block tip = self.generate(self.nodes[0], 1)[0] assert_equal(self.nodes[0].getrawtransaction(fund_txid, 1, tip)['confirmations'], 1) - protx_result = self.nodes[0].protx('revoke', revoke_protx, revoke_keyoperator, 1, funds_address) + protx_result = revoke_mn.revoke(self.nodes[0], submit=True, reason=1, fundsAddr=funds_address) self.bump_mocktime(10 * 60 + 1) # to make tx safe to include in block tip = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[0] assert_equal(self.nodes[0].getrawtransaction(protx_result, 1, tip)['confirmations'], 1) @@ -132,9 +130,9 @@ def test_revoke_protx(self, node_idx, revoke_protx, revoke_keyoperator): self.wait_until(lambda: self.nodes[node_idx].getconnectioncount() == 0) self.connect_nodes(node_idx, 0) self.sync_all() - self.log.info(f"Successfully revoked={revoke_protx}") + self.log.info(f"Successfully revoked={revoke_mn.proTxHash}") for mn in self.mninfo: # type: MasternodeInfo - if mn.proTxHash == revoke_protx: + if mn.proTxHash == revoke_mn.proTxHash: self.mninfo.remove(mn) return diff --git a/test/functional/feature_llmq_simplepose.py b/test/functional/feature_llmq_simplepose.py index 12d92c063c41..d5ea7288ac01 100755 --- a/test/functional/feature_llmq_simplepose.py +++ b/test/functional/feature_llmq_simplepose.py @@ -208,7 +208,7 @@ def repair_masternodes(self, restart): if check_banned(self.nodes[0], mn) or check_punished(self.nodes[0], mn): addr = self.nodes[0].getnewaddress() self.nodes[0].sendtoaddress(addr, 0.1) - self.nodes[0].protx('update_service', mn.proTxHash, f'127.0.0.1:{mn.nodePort}', mn.keyOperator, "", addr) + mn.update_service(self.nodes[0], submit=True, fundsAddr=addr) if restart: self.stop_node(mn.nodeIdx) self.start_masternode(mn) diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 2f1215bec3c3..836821d86c2f 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -229,7 +229,7 @@ def check_ehf_activated(self): self.bump_mocktime(1) self.generate(self.nodes[1], 1) return get_bip9_details(self.nodes[0], 'testdummy')['status'] == 'active' - self.wait_until(lambda: check_ehf_activated(self)) + self.wait_until(lambda: check_ehf_activated(self), sleep=1) if __name__ == '__main__': MnehfTest().main() diff --git a/test/functional/rpc_scanblocks.py b/test/functional/rpc_scanblocks.py new file mode 100644 index 000000000000..d05ba09ba520 --- /dev/null +++ b/test/functional/rpc_scanblocks.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021-2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test the scanblocks RPC call.""" +from test_framework.address import address_to_scriptpubkey +from test_framework.blockfilter import ( + bip158_basic_element_hash, + bip158_relevant_scriptpubkeys, +) +from test_framework.messages import COIN +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) +from test_framework.wallet import ( + MiniWallet, + getnewdestination, +) + + +class ScanblocksTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 2 + self.extra_args = [["-blockfilterindex=1"], []] + + def run_test(self): + node = self.nodes[0] + wallet = MiniWallet(node) + + # send 1.0, mempool only + _, spk_1, addr_1 = getnewdestination() + wallet.send_to(from_node=node, scriptPubKey=spk_1, amount=1 * COIN) + + parent_key = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B" + # send 1.0, mempool only + # childkey 5 of `parent_key` + wallet.send_to(from_node=node, + scriptPubKey=address_to_scriptpubkey("mkS4HXoTYWRTescLGaUTGbtTTYX5EjJyEE"), + amount=1 * COIN) + + # mine a block and assure that the mined blockhash is in the filterresult + blockhash = self.generate(node, 1)[0] + height = node.getblockheader(blockhash)['height'] + self.wait_until(lambda: all(i["synced"] for i in node.getindexinfo().values())) + + out = node.scanblocks("start", [f"addr({addr_1})"]) + assert blockhash in out['relevant_blocks'] + assert_equal(height, out['to_height']) + assert_equal(0, out['from_height']) + assert_equal(True, out['completed']) + + # mine another block + blockhash_new = self.generate(node, 1)[0] + height_new = node.getblockheader(blockhash_new)['height'] + + # make sure the blockhash is not in the filter result if we set the start_height + # to the just mined block (unlikely to hit a false positive) + assert blockhash not in node.scanblocks( + "start", [f"addr({addr_1})"], height_new)['relevant_blocks'] + + # make sure the blockhash is present when using the first mined block as start_height + assert blockhash in node.scanblocks( + "start", [f"addr({addr_1})"], height)['relevant_blocks'] + for v in [False, True]: + assert blockhash in node.scanblocks( + action="start", + scanobjects=[f"addr({addr_1})"], + start_height=height, + options={"filter_false_positives": v})['relevant_blocks'] + + # also test the stop height + assert blockhash in node.scanblocks( + "start", [f"addr({addr_1})"], height, height)['relevant_blocks'] + + # use the stop_height to exclude the relevant block + assert blockhash not in node.scanblocks( + "start", [f"addr({addr_1})"], 0, height - 1)['relevant_blocks'] + + # make sure the blockhash is present when using the first mined block as start_height + assert blockhash in node.scanblocks( + "start", [{"desc": f"pkh({parent_key}/*)", "range": [0, 100]}], height)['relevant_blocks'] + + # check that false-positives are included in the result now; note that + # finding a false-positive at runtime would take too long, hence we simply + # use a pre-calculated one that collides with the regtest genesis block's + # coinbase output and verify that their BIP158 ranged hashes match + genesis_blockhash = node.getblockhash(0) + genesis_spks = bip158_relevant_scriptpubkeys(node, genesis_blockhash) + assert_equal(len(genesis_spks), 1) + genesis_coinbase_spk = list(genesis_spks)[0] + false_positive_spk = bytes.fromhex("001400000000000000000000000000000000000cadcb") + + genesis_coinbase_hash = bip158_basic_element_hash(genesis_coinbase_spk, 1, genesis_blockhash) + false_positive_hash = bip158_basic_element_hash(false_positive_spk, 1, genesis_blockhash) + assert_equal(genesis_coinbase_hash, false_positive_hash) + + assert genesis_blockhash in node.scanblocks( + "start", [{"desc": f"raw({genesis_coinbase_spk.hex()})"}], 0, 0)['relevant_blocks'] + assert genesis_blockhash in node.scanblocks( + "start", [{"desc": f"raw({false_positive_spk.hex()})"}], 0, 0)['relevant_blocks'] + + # check that the filter_false_positives option works + assert genesis_blockhash in node.scanblocks( + "start", [{"desc": f"raw({genesis_coinbase_spk.hex()})"}], 0, 0, "basic", {"filter_false_positives": True})['relevant_blocks'] + assert genesis_blockhash not in node.scanblocks( + "start", [{"desc": f"raw({false_positive_spk.hex()})"}], 0, 0, "basic", {"filter_false_positives": True})['relevant_blocks'] + + # test node with disabled blockfilterindex + assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", + self.nodes[1].scanblocks, "start", [f"addr({addr_1})"]) + + # test unknown filtertype + assert_raises_rpc_error(-5, "Unknown filtertype", + node.scanblocks, "start", [f"addr({addr_1})"], 0, 10, "extended") + + # test invalid start_height + assert_raises_rpc_error(-1, "Invalid start_height", + node.scanblocks, "start", [f"addr({addr_1})"], 100000000) + + # test invalid stop_height + assert_raises_rpc_error(-1, "Invalid stop_height", + node.scanblocks, "start", [f"addr({addr_1})"], 10, 0) + assert_raises_rpc_error(-1, "Invalid stop_height", + node.scanblocks, "start", [f"addr({addr_1})"], 10, 100000000) + + # test accessing the status (must be empty) + assert_equal(node.scanblocks("status"), None) + + # test aborting the current scan (there is no, must return false) + assert_equal(node.scanblocks("abort"), False) + + # test invalid command + assert_raises_rpc_error(-8, "Invalid action 'foobar'", node.scanblocks, "foobar") + + +if __name__ == '__main__': + ScanblocksTest(__file__).main() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index f547ee084edb..ca98429fac64 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -35,8 +35,6 @@ ser_compact_size, ser_string, tx_from_hex, - - ) from .script import hash160 from .p2p import NetworkThread @@ -46,6 +44,7 @@ MAX_NODES, append_config, assert_equal, + assert_raises_rpc_error, check_json_precision, copy_datadir, force_finish_mnsync, @@ -897,7 +896,7 @@ def _initialize_mocktime(self, is_genesis): for node in self.nodes: node.mocktime = self.mocktime - def wait_until(self, test_function, timeout=60, lock=None, sleep=0.5, do_assert=True): + def wait_until(self, test_function, timeout=60, lock=None, sleep=0.05, do_assert=True): return wait_until_helper(test_function, timeout=timeout, lock=lock, timeout_factor=self.options.timeout_factor, sleep=sleep, do_assert=do_assert) # Private helper methods. These should not be accessed by the subclass test scripts. @@ -1186,6 +1185,8 @@ def get_collateral_vout(self, node: TestNode, txid: str, should_throw: bool = Tr return -1 def __init__(self, evo: bool, legacy: bool): + if evo and legacy: + raise AssertionError("EvoNodes are not allowed to use legacy scheme") self.evo = evo self.legacy = legacy @@ -1204,6 +1205,7 @@ def set_params(self, proTxHash: Optional[str] = None, operator_reward: Optional[ def set_node(self, nodeIdx: int, friendlyName: Optional[str] = None): self.nodeIdx = nodeIdx + self.nodePort = p2p_port(nodeIdx) self.friendlyName = friendlyName or f"mn-{'evo' if self.evo else 'reg'}-{self.nodeIdx}" def get_node(self, test: BitcoinTestFramework) -> TestNode: @@ -1213,6 +1215,257 @@ def get_node(self, test: BitcoinTestFramework) -> TestNode: raise AssertionError(f"Node at pos {self.nodeIdx} not present, did you start the node?") return test.nodes[self.nodeIdx] + def register(self, node: TestNode, submit: bool, collateral_txid: Optional[str] = None, collateral_vout: Optional[int] = None, + ipAndPort: Optional[str] = None, ownerAddr: Optional[str] = None, pubKeyOperator: Optional[str] = None, votingAddr: Optional[str] = None, + operator_reward: Optional[int] = None, rewards_address: Optional[str] = None, fundsAddr: Optional[str] = None, + platform_node_id: Optional[str] = None, platform_p2p_port: Optional[int] = None, platform_http_port: Optional[int] = None, + expected_assert_code: Optional[int] = None, expected_assert_msg: Optional[str] = None) -> Optional[str]: + if (expected_assert_code and not expected_assert_msg) or (not expected_assert_code and expected_assert_msg): + raise AssertionError("Intending to use assert_raises_rpc_error() but didn't specify code and message") + + # EvoNode-specific fields are ignored for regular masternodes + if self.evo: + if platform_node_id is None: + raise AssertionError("EvoNode but platform_node_id is missing, must be specified!") + if platform_p2p_port is None: + raise AssertionError("EvoNode but platform_p2p_port is missing, must be specified!") + if platform_http_port is None: + raise AssertionError("EvoNode but platform_http_port is missing, must be specified!") + + # Common arguments shared between regular masternodes and EvoNodes + args = [ + collateral_txid or self.collateral_txid, + collateral_vout or self.collateral_vout, + ipAndPort or f'127.0.0.1:{self.nodePort}', + ownerAddr or self.ownerAddr, + pubKeyOperator or self.pubKeyOperator, + votingAddr or self.votingAddr, + operator_reward or self.operator_reward, + rewards_address or self.rewards_address, + ] + address_funds = fundsAddr or self.fundsAddr + + # Use assert_raises_rpc_error if we expect to error out + use_assert: bool = bool(expected_assert_code and expected_assert_msg) + + # Flip a coin and decide if we will submit the transaction directly or use sendrawtransaction + use_srt: bool = bool(random.getrandbits(1)) and submit and not use_assert + if use_srt: + submit = False + + # Construct final command and arguments + if self.evo: + command = "register_evo" + args = args + [platform_node_id, platform_p2p_port, platform_http_port, address_funds, submit] # type: ignore + else: + command = "register_legacy" if self.legacy else "register" + args = args + [address_funds, submit] # type: ignore + + if use_assert: + assert_raises_rpc_error(expected_assert_code, expected_assert_msg, node.protx, command, *args) + return None + + ret: str = node.protx(command, *args) + if use_srt: + ret = node.sendrawtransaction(ret) + + return ret + + def register_fund(self, node: TestNode, submit: bool, collateral_address: Optional[str] = None, ipAndPort: Optional[str] = None, + ownerAddr: Optional[str] = None, pubKeyOperator: Optional[str] = None, votingAddr: Optional[str] = None, + operator_reward: Optional[int] = None, rewards_address: Optional[str] = None, fundsAddr: Optional[str] = None, + platform_node_id: Optional[str] = None, platform_p2p_port: Optional[int] = None, platform_http_port: Optional[int] = None, + expected_assert_code: Optional[int] = None, expected_assert_msg: Optional[str] = None) -> Optional[str]: + if (expected_assert_code and not expected_assert_msg) or (not expected_assert_code and expected_assert_msg): + raise AssertionError("Intending to use assert_raises_rpc_error() but didn't specify code and message") + + # EvoNode-specific fields are ignored for regular masternodes + if self.evo: + if platform_node_id is None: + raise AssertionError("EvoNode but platform_node_id is missing, must be specified!") + if platform_p2p_port is None: + raise AssertionError("EvoNode but platform_p2p_port is missing, must be specified!") + if platform_http_port is None: + raise AssertionError("EvoNode but platform_http_port is missing, must be specified!") + + # Use assert_raises_rpc_error if we expect to error out + use_assert: bool = bool(expected_assert_code and expected_assert_msg) + + # Flip a coin and decide if we will submit the transaction directly or use sendrawtransaction + use_srt: bool = bool(random.getrandbits(1)) and submit and not use_assert + if use_srt: + submit = False + + # Common arguments shared between regular masternodes and EvoNodes + args = [ + collateral_address or self.collateral_address, + ipAndPort or f'127.0.0.1:{self.nodePort}', + ownerAddr or self.ownerAddr, + pubKeyOperator or self.pubKeyOperator, + votingAddr or self.votingAddr, + operator_reward or self.operator_reward, + rewards_address or self.rewards_address, + ] + address_funds = fundsAddr or self.fundsAddr + + # Construct final command and arguments + if self.evo: + command = "register_fund_evo" + args = args + [platform_node_id, platform_p2p_port, platform_http_port, address_funds, submit] # type: ignore + else: + command = "register_fund_legacy" if self.legacy else "register_fund" + args = args + [address_funds, submit] # type: ignore + + if use_assert: + assert_raises_rpc_error(expected_assert_code, expected_assert_msg, node.protx, command, *args) + return None + + ret: str = node.protx(command, *args) + if use_srt: + ret = node.sendrawtransaction(ret) + + return ret + + def revoke(self, node: TestNode, submit: bool, reason: int, fundsAddr: Optional[str] = None, + expected_assert_code: Optional[int] = None, expected_assert_msg: Optional[str] = None) -> Optional[str]: + if (expected_assert_code and not expected_assert_msg) or (not expected_assert_code and expected_assert_msg): + raise AssertionError("Intending to use assert_raises_rpc_error() but didn't specify code and message") + + # Update commands should be run from the appropriate MasternodeInfo instance, we do not allow overriding some values for this reason + if self.proTxHash is None: + raise AssertionError("proTxHash not set, did you call set_params()") + if self.keyOperator is None: + raise AssertionError("keyOperator not set, did you call generate_addresses()") + + # Use assert_raises_rpc_error if we expect to error out + use_assert: bool = bool(expected_assert_code and expected_assert_msg) + + # Flip a coin and decide if we will submit the transaction directly or use sendrawtransaction + use_srt: bool = bool(random.getrandbits(1)) and submit and (fundsAddr is not None) and not use_assert + if use_srt: + submit = False + + command = 'revoke' + args = [ + self.proTxHash, + self.keyOperator, + reason, + ] + + # fundsAddr is an optional field that results in different behavior if omitted, so we don't fallback here + if fundsAddr is not None: + args = args + [fundsAddr, submit] # type: ignore + elif submit is not True: + raise AssertionError("Cannot withhold transaction if relying on fundsAddr fallback behavior") + + if use_assert: + assert_raises_rpc_error(expected_assert_code, expected_assert_msg, node.protx, command, *args) + return None + + ret: str = node.protx(command, *args) + if use_srt: + ret = node.sendrawtransaction(ret) + + return ret + + def update_registrar(self, node: TestNode, submit: bool, pubKeyOperator: Optional[str] = None, votingAddr: Optional[str] = None, + rewards_address: Optional[str] = None, fundsAddr: Optional[str] = None, + expected_assert_code: Optional[int] = None, expected_assert_msg: Optional[str] = None) -> Optional[str]: + if (expected_assert_code and not expected_assert_msg) or (not expected_assert_code and expected_assert_msg): + raise AssertionError("Intending to use assert_raises_rpc_error() but didn't specify code and message") + + # Update commands should be run from the appropriate MasternodeInfo instance, we do not allow overriding proTxHash for this reason + if self.proTxHash is None: + raise AssertionError("proTxHash not set, did you call set_params()") + + # Use assert_raises_rpc_error if we expect to error out + use_assert: bool = bool(expected_assert_code and expected_assert_msg) + + # Flip a coin and decide if we will submit the transaction directly or use sendrawtransaction + use_srt: bool = bool(random.getrandbits(1)) and submit and (fundsAddr is not None) and not use_assert + if use_srt: + submit = False + + command = 'update_registrar_legacy' if self.legacy else 'update_registrar' + args = [ + self.proTxHash, + pubKeyOperator or self.pubKeyOperator, + votingAddr or self.votingAddr, + rewards_address or self.rewards_address, + ] + + # fundsAddr is an optional field that results in different behavior if omitted, so we don't fallback here + if fundsAddr is not None: + args = args + [fundsAddr, submit] # type: ignore + elif submit is not True: + raise AssertionError("Cannot withhold transaction if relying on fundsAddr fallback behavior") + + if use_assert: + assert_raises_rpc_error(expected_assert_code, expected_assert_msg, node.protx, command, *args) + return None + + ret: str = node.protx(command, *args) + if use_srt: + ret = node.sendrawtransaction(ret) + + return ret + + def update_service(self, node: TestNode, submit: bool, ipAndPort: Optional[str] = None, platform_node_id: Optional[str] = None, platform_p2p_port: Optional[int] = None, + platform_http_port: Optional[int] = None, address_operator: Optional[str] = None, fundsAddr: Optional[str] = None, + expected_assert_code: Optional[int] = None, expected_assert_msg: Optional[str] = None) -> Optional[str]: + if (expected_assert_code and not expected_assert_msg) or (not expected_assert_code and expected_assert_msg): + raise AssertionError("Intending to use assert_raises_rpc_error() but didn't specify code and message") + + # Update commands should be run from the appropriate MasternodeInfo instance, we do not allow overriding some values for this reason + if self.proTxHash is None: + raise AssertionError("proTxHash not set, did you call set_params()") + if self.keyOperator is None: + raise AssertionError("keyOperator not set, did you call generate_addresses()") + + # EvoNode-specific fields are ignored for regular masternodes + if self.evo: + if platform_node_id is None: + raise AssertionError("EvoNode but platform_node_id is missing, must be specified!") + if platform_p2p_port is None: + raise AssertionError("EvoNode but platform_p2p_port is missing, must be specified!") + if platform_http_port is None: + raise AssertionError("EvoNode but platform_http_port is missing, must be specified!") + + # Use assert_raises_rpc_error if we expect to error out + use_assert: bool = bool(expected_assert_code and expected_assert_msg) + + # Flip a coin and decide if we will submit the transaction directly or use sendrawtransaction + use_srt: bool = bool(random.getrandbits(1)) and submit and not use_assert + if use_srt: + submit = False + + # Common arguments shared between regular masternodes and EvoNodes + args = [ + self.proTxHash, + ipAndPort or f'127.0.0.1:{self.nodePort}', + self.keyOperator, + ] + address_funds = fundsAddr or self.fundsAddr + address_operator = address_operator or "" + + # Construct final command and arguments + if self.evo: + command = "update_service_evo" + args = args + [platform_node_id, platform_p2p_port, platform_http_port, address_operator, address_funds, submit] # type: ignore + else: + command = "update_service" + args = args + [address_operator, address_funds, submit] # type: ignore + + if use_assert: + assert_raises_rpc_error(expected_assert_code, expected_assert_msg, node.protx, command, *args) + return None + + ret: str = node.protx(command, *args) + if use_srt: + ret = node.sendrawtransaction(ret) + + return ret + class DashTestFramework(BitcoinTestFramework): def set_test_params(self): """Tests must this method to change default values for number of nodes, topology, etc""" @@ -1381,8 +1634,8 @@ def dynamically_prepare_masternode(self, idx, node_p2p_port, evo=False, rnd=None mn.generate_addresses(self.nodes[0]) platform_node_id = hash160(b'%d' % rnd).hex() if rnd is not None else hash160(b'%d' % node_p2p_port).hex() - platform_p2p_port = '%d' % (node_p2p_port + 101) - platform_http_port = '%d' % (node_p2p_port + 102) + platform_p2p_port = node_p2p_port + 101 + platform_http_port = node_p2p_port + 102 outputs = {mn.collateral_address: mn.get_collateral_value(), mn.fundsAddr: 1} collateral_txid = self.nodes[0].sendmany("", outputs) @@ -1393,11 +1646,10 @@ def dynamically_prepare_masternode(self, idx, node_p2p_port, evo=False, rnd=None ipAndPort = '127.0.0.1:%d' % node_p2p_port operatorReward = idx - protx_result = None - if evo: - protx_result = self.nodes[0].protx("register_evo", collateral_txid, collateral_vout, ipAndPort, mn.ownerAddr, mn.pubKeyOperator, mn.votingAddr, operatorReward, mn.rewards_address, platform_node_id, platform_p2p_port, platform_http_port, mn.fundsAddr, True) - else: - protx_result = self.nodes[0].protx("register_legacy" if mn.legacy else "register", collateral_txid, collateral_vout, ipAndPort, mn.ownerAddr, mn.pubKeyOperator, mn.votingAddr, operatorReward, mn.rewards_address, mn.fundsAddr, True) + # platform_node_id, platform_p2p_port and platform_http_port are ignored for regular masternodes + protx_result = mn.register(self.nodes[0], submit=True, collateral_txid=collateral_txid, collateral_vout=collateral_vout, ipAndPort=ipAndPort, operator_reward=operatorReward, + platform_node_id=platform_node_id, platform_p2p_port=platform_p2p_port, platform_http_port=platform_http_port) + assert protx_result is not None self.bump_mocktime(10 * 60 + 1) # to make tx safe to include in block mn.bury_tx(self, genIdx=0, txid=protx_result, depth=1) @@ -1416,8 +1668,8 @@ def dynamically_evo_update_service(self, evo_info: MasternodeInfo, rnd=None, sho # For the sake of the test, generate random nodeid, p2p and http platform values r = rnd if rnd is not None else random.randint(21000, 65000) platform_node_id = hash160(b'%d' % r).hex() - platform_p2p_port = '%d' % (r + 1) - platform_http_port = '%d' % (r + 2) + platform_p2p_port = r + 1 + platform_http_port = r + 2 fund_txid = self.nodes[0].sendtoaddress(funds_address, 1) self.bump_mocktime(10 * 60 + 1) # to make tx safe to include in block @@ -1425,7 +1677,8 @@ def dynamically_evo_update_service(self, evo_info: MasternodeInfo, rnd=None, sho protx_success = False try: - protx_result = self.nodes[0].protx('update_service_evo', evo_info.proTxHash, f'127.0.0.1:{evo_info.nodePort}', evo_info.keyOperator, platform_node_id, platform_p2p_port, platform_http_port, operator_reward_address, funds_address) + protx_result = evo_info.update_service(self.nodes[0], True, f'127.0.0.1:{evo_info.nodePort}', platform_node_id, platform_p2p_port, platform_http_port, operator_reward_address, funds_address) + assert protx_result is not None self.bump_mocktime(10 * 60 + 1) # to make tx safe to include in block evo_info.bury_tx(self, genIdx=0, txid=protx_result, depth=1) self.log.info("Updated EvoNode %s: platformNodeID=%s, platformP2PPort=%s, platformHTTPPort=%s" % (evo_info.proTxHash, platform_node_id, platform_p2p_port, platform_http_port)) @@ -1462,24 +1715,23 @@ def prepare_masternode(self, idx): submit = (idx % 4) < 2 if register_fund: - protx_result = self.nodes[0].protx('register_fund_legacy' if mn.legacy else 'register_fund', mn.collateral_address, ipAndPort, - mn.ownerAddr, mn.pubKeyOperator, mn.votingAddr, operatorReward, mn.rewards_address, mn.fundsAddr, submit) + protx_result = mn.register_fund(self.nodes[0], submit=submit, ipAndPort=ipAndPort, operator_reward=operatorReward) else: self.generate(self.nodes[0], 1, sync_fun=self.no_op) - protx_result = self.nodes[0].protx('register_legacy' if mn.legacy else 'register', txid, collateral_vout, ipAndPort, - mn.ownerAddr, mn.pubKeyOperator, mn.votingAddr, operatorReward, mn.rewards_address, mn.fundsAddr, submit) - + protx_result = mn.register(self.nodes[0], submit=submit, collateral_txid=txid, collateral_vout=collateral_vout, ipAndPort=ipAndPort, + operator_reward=operatorReward) if submit: proTxHash = protx_result else: proTxHash = self.nodes[0].sendrawtransaction(protx_result) + mn.set_params(proTxHash=proTxHash, operator_reward=operatorReward, collateral_txid=txid, collateral_vout=collateral_vout, nodePort=port) + if operatorReward > 0: self.generate(self.nodes[0], 1, sync_fun=self.no_op) operatorPayoutAddress = self.nodes[0].getnewaddress() - self.nodes[0].protx('update_service', proTxHash, ipAndPort, mn.keyOperator, operatorPayoutAddress, mn.fundsAddr) + mn.update_service(self.nodes[0], submit=True, ipAndPort=ipAndPort, address_operator=operatorPayoutAddress) - mn.set_params(proTxHash=proTxHash, operator_reward=operatorReward, collateral_txid=txid, collateral_vout=collateral_vout, nodePort=port) self.mninfo.append(mn) self.log.info("Prepared MN %d: collateral_txid=%s, collateral_vout=%d, protxHash=%s" % (idx, txid, collateral_vout, proTxHash)) @@ -1717,7 +1969,7 @@ def check_sporks_same(): self.bump_mocktime(1) sporks = self.nodes[0].spork('show') return all(node.spork('show') == sporks for node in self.nodes[1:]) - self.wait_until(check_sporks_same, timeout=timeout, sleep=0.5) + self.wait_until(check_sporks_same, timeout=timeout, sleep=1) def wait_for_quorum_connections(self, quorum_hash, expected_connections, mninfos, llmq_type_name="llmq_test", timeout = 60, wait_proc=None): def check_quorum_connections(): @@ -1839,26 +2091,18 @@ def check_dkg_comitments(): self.wait_until(check_dkg_comitments, timeout=timeout, sleep=1) - def wait_for_quorum_list(self, quorum_hash, nodes, timeout=15, sleep=2, llmq_type_name="llmq_test"): + def wait_for_quorum_list(self, quorum_hash, nodes, timeout=15, llmq_type_name="llmq_test"): def wait_func(): - self.log.info("quorums: " + str(self.nodes[0].quorum("list"))) - if quorum_hash in self.nodes[0].quorum("list")[llmq_type_name]: - return True - self.bump_mocktime(sleep, nodes=nodes) - self.generate(self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(nodes)) - return False - self.wait_until(wait_func, timeout=timeout, sleep=sleep) + return quorum_hash in self.nodes[0].quorum('list')[llmq_type_name] + self.log.info(f"quorums: {self.nodes[0].quorum('list')}") + self.wait_until(wait_func, timeout=timeout, sleep=0.05) - def wait_for_quorums_list(self, quorum_hash_0, quorum_hash_1, nodes, llmq_type_name="llmq_test", timeout=15, sleep=2): + def wait_for_quorums_list(self, quorum_hash_0, quorum_hash_1, nodes, llmq_type_name="llmq_test", timeout=15): def wait_func(): - self.log.info("h("+str(self.nodes[0].getblockcount())+") quorums: " + str(self.nodes[0].quorum("list"))) - if quorum_hash_0 in self.nodes[0].quorum("list")[llmq_type_name]: - if quorum_hash_1 in self.nodes[0].quorum("list")[llmq_type_name]: - return True - self.bump_mocktime(sleep, nodes=nodes) - self.generate(self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(nodes)) - return False - self.wait_until(wait_func, timeout=timeout, sleep=sleep) + quorums = self.nodes[0].quorum("list")[llmq_type_name] + return quorum_hash_0 in quorums and quorum_hash_1 in quorums + self.log.info(f"h({self.nodes[0].getblockcount()}) quorums: {self.nodes[0].quorum('list')}") + self.wait_until(wait_func, timeout=timeout, sleep=0.05) def move_blocks(self, nodes, num_blocks): self.bump_mocktime(1, nodes=nodes) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index e677f63f51af..8a3d59b8745d 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -247,7 +247,7 @@ def satoshi_round(amount): return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) -def wait_until_helper(predicate, *, attempts=float('inf'), timeout=float('inf'), sleep=0.5, timeout_factor=1.0, lock=None, do_assert=True, allow_exception=False): +def wait_until_helper(predicate, *, attempts=float('inf'), timeout=float('inf'), sleep=0.05, timeout_factor=1.0, lock=None, do_assert=True, allow_exception=False): """Sleep until the predicate resolves to be True. Warning: Note that this method is not recommended to be used in tests as it is diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index ff0da91247fc..ceef8d326677 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -367,6 +367,7 @@ 'rpc_deriveaddresses.py --usecli', 'p2p_ping.py', 'p2p_sendtxrcncl.py', + 'rpc_scanblocks.py', 'rpc_scantxoutset.py', 'feature_txindex_compatibility.py', 'feature_logging.py',