diff options
-rw-r--r-- | src/bitcoin-cli.cpp | 4 | ||||
-rw-r--r-- | src/rpc/blockchain.cpp | 59 | ||||
-rw-r--r-- | src/rpc/blockchain.h | 9 | ||||
-rw-r--r-- | src/test/rpc_tests.cpp | 80 | ||||
-rw-r--r-- | test/functional/data/rpc_getblockstats.json | 24 | ||||
-rwxr-xr-x | test/functional/feature_cltv.py | 4 | ||||
-rwxr-xr-x | test/functional/feature_csv_activation.py | 13 | ||||
-rwxr-xr-x | test/functional/feature_dersig.py | 7 | ||||
-rwxr-xr-x | test/functional/feature_nulldummy.py | 12 | ||||
-rwxr-xr-x | test/functional/mempool_reorg.py | 12 | ||||
-rwxr-xr-x | test/functional/mempool_resurrect.py | 12 | ||||
-rwxr-xr-x | test/functional/mempool_spend_coinbase.py | 6 | ||||
-rwxr-xr-x | test/functional/rpc_getblockstats.py | 2 | ||||
-rw-r--r-- | test/functional/test_framework/blocktools.py | 12 | ||||
-rwxr-xr-x | test/lint/lint-format-strings.py | 38 |
15 files changed, 238 insertions, 56 deletions
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 9515a658b3..db713f58d2 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -49,8 +49,8 @@ static void SetupCliArgs() gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcwait", "Wait for RPC server to start", false, OptionsCategory::OPTIONS); gArgs.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind)", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-stdin", "Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password.", false, OptionsCategory::OPTIONS); - gArgs.AddArg("-stdinrpcpass", strprintf("Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password."), false, OptionsCategory::OPTIONS); + gArgs.AddArg("-stdin", "Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password.", false, OptionsCategory::OPTIONS); + gArgs.AddArg("-stdinrpcpass", "Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password.", false, OptionsCategory::OPTIONS); // Hidden gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 8bf6030833..51bc218d39 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1640,6 +1640,35 @@ static T CalculateTruncatedMedian(std::vector<T>& scores) } } +void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight) +{ + if (scores.empty()) { + return; + } + + std::sort(scores.begin(), scores.end()); + + // 10th, 25th, 50th, 75th, and 90th percentile weight units. + const double weights[NUM_GETBLOCKSTATS_PERCENTILES] = { + total_weight / 10.0, total_weight / 4.0, total_weight / 2.0, (total_weight * 3.0) / 4.0, (total_weight * 9.0) / 10.0 + }; + + int64_t next_percentile_index = 0; + int64_t cumulative_weight = 0; + for (const auto& element : scores) { + cumulative_weight += element.second; + while (next_percentile_index < NUM_GETBLOCKSTATS_PERCENTILES && cumulative_weight >= weights[next_percentile_index]) { + result[next_percentile_index] = element.first; + ++next_percentile_index; + } + } + + // Fill any remaining percentiles with the last value. + for (int64_t i = next_percentile_index; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) { + result[i] = scores.back().first; + } +} + template<typename T> static inline bool SetHasKeys(const std::set<T>& set) {return false;} template<typename T, typename Tk, typename... Args> @@ -1673,13 +1702,19 @@ static UniValue getblockstats(const JSONRPCRequest& request) " \"avgfeerate\": xxxxx, (numeric) Average feerate (in satoshis per virtual byte)\n" " \"avgtxsize\": xxxxx, (numeric) Average transaction size\n" " \"blockhash\": xxxxx, (string) The block hash (to check for potential reorgs)\n" + " \"feerate_percentiles\": [ (array of numeric) Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)\n" + " \"10th_percentile_feerate\", (numeric) The 10th percentile feerate\n" + " \"25th_percentile_feerate\", (numeric) The 25th percentile feerate\n" + " \"50th_percentile_feerate\", (numeric) The 50th percentile feerate\n" + " \"75th_percentile_feerate\", (numeric) The 75th percentile feerate\n" + " \"90th_percentile_feerate\", (numeric) The 90th percentile feerate\n" + " ],\n" " \"height\": xxxxx, (numeric) The height of the block\n" " \"ins\": xxxxx, (numeric) The number of inputs (excluding coinbase)\n" " \"maxfee\": xxxxx, (numeric) Maximum fee in the block\n" " \"maxfeerate\": xxxxx, (numeric) Maximum feerate (in satoshis per virtual byte)\n" " \"maxtxsize\": xxxxx, (numeric) Maximum transaction size\n" " \"medianfee\": xxxxx, (numeric) Truncated median fee in the block\n" - " \"medianfeerate\": xxxxx, (numeric) Truncated median feerate (in satoshis per virtual byte)\n" " \"mediantime\": xxxxx, (numeric) The block median time past\n" " \"mediantxsize\": xxxxx, (numeric) Truncated median transaction size\n" " \"minfee\": xxxxx, (numeric) Minimum fee in the block\n" @@ -1747,13 +1782,13 @@ static UniValue getblockstats(const JSONRPCRequest& request) const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default) const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0; const bool do_medianfee = do_all || stats.count("medianfee") != 0; - const bool do_medianfeerate = do_all || stats.count("medianfeerate") != 0; - const bool loop_inputs = do_all || do_medianfee || do_medianfeerate || + const bool do_feerate_percentiles = do_all || stats.count("feerate_percentiles") != 0; + const bool loop_inputs = do_all || do_medianfee || do_feerate_percentiles || SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate"); const bool loop_outputs = do_all || loop_inputs || stats.count("total_out"); const bool do_calculate_size = do_mediantxsize || SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize", "swtotal_size"); - const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "medianfeerate", "minfeerate", "maxfeerate"); + const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "feerate_percentiles", "minfeerate", "maxfeerate"); const bool do_calculate_sw = do_all || SetHasKeys(stats, "swtxs", "swtotal_size", "swtotal_weight"); CAmount maxfee = 0; @@ -1773,7 +1808,7 @@ static UniValue getblockstats(const JSONRPCRequest& request) int64_t total_weight = 0; int64_t utxo_size_inc = 0; std::vector<CAmount> fee_array; - std::vector<CAmount> feerate_array; + std::vector<std::pair<CAmount, int64_t>> feerate_array; std::vector<int64_t> txsize_array; for (const auto& tx : block.vtx) { @@ -1848,26 +1883,34 @@ static UniValue getblockstats(const JSONRPCRequest& request) // New feerate uses satoshis per virtual byte instead of per serialized byte CAmount feerate = weight ? (txfee * WITNESS_SCALE_FACTOR) / weight : 0; - if (do_medianfeerate) { - feerate_array.push_back(feerate); + if (do_feerate_percentiles) { + feerate_array.emplace_back(std::make_pair(feerate, weight)); } maxfeerate = std::max(maxfeerate, feerate); minfeerate = std::min(minfeerate, feerate); } } + CAmount feerate_percentiles[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 }; + CalculatePercentilesByWeight(feerate_percentiles, feerate_array, total_weight); + + UniValue feerates_res(UniValue::VARR); + for (int64_t i = 0; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) { + feerates_res.push_back(feerate_percentiles[i]); + } + UniValue ret_all(UniValue::VOBJ); ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0); ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0); ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex()); + ret_all.pushKV("feerate_percentiles", feerates_res); ret_all.pushKV("height", (int64_t)pindex->nHeight); ret_all.pushKV("ins", inputs); ret_all.pushKV("maxfee", maxfee); ret_all.pushKV("maxfeerate", maxfeerate); ret_all.pushKV("maxtxsize", maxtxsize); ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array)); - ret_all.pushKV("medianfeerate", CalculateTruncatedMedian(feerate_array)); ret_all.pushKV("mediantime", pindex->GetMedianTimePast()); ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array)); ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee); diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index c664139ed3..544bc62c36 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -5,10 +5,16 @@ #ifndef BITCOIN_RPC_BLOCKCHAIN_H #define BITCOIN_RPC_BLOCKCHAIN_H +#include <vector> +#include <stdint.h> +#include <amount.h> + class CBlock; class CBlockIndex; class UniValue; +static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5; + /** * Get the difficulty of the net wrt to the given block index, or the chain tip if * not provided. @@ -33,4 +39,7 @@ UniValue mempoolToJSON(bool fVerbose = false); /** Block header to JSON */ UniValue blockheaderToJSON(const CBlockIndex* blockindex); +/** Used by getblockstats to get feerates at different percentiles by weight */ +void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight); + #endif diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index d0f6fba78d..a49796d6f4 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -16,6 +16,8 @@ #include <univalue.h> +#include <rpc/blockchain.h> + UniValue CallRPC(std::string args) { std::vector<std::string> vArgs; @@ -336,4 +338,82 @@ BOOST_AUTO_TEST_CASE(rpc_convert_values_generatetoaddress) BOOST_CHECK_EQUAL(result[2].get_int(), 9); } +BOOST_AUTO_TEST_CASE(rpc_getblockstats_calculate_percentiles_by_weight) +{ + int64_t total_weight = 200; + std::vector<std::pair<CAmount, int64_t>> feerates; + CAmount result[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 }; + + for (int64_t i = 0; i < 100; i++) { + feerates.emplace_back(std::make_pair(1 ,1)); + } + + for (int64_t i = 0; i < 100; i++) { + feerates.emplace_back(std::make_pair(2 ,1)); + } + + CalculatePercentilesByWeight(result, feerates, total_weight); + BOOST_CHECK_EQUAL(result[0], 1); + BOOST_CHECK_EQUAL(result[1], 1); + BOOST_CHECK_EQUAL(result[2], 1); + BOOST_CHECK_EQUAL(result[3], 2); + BOOST_CHECK_EQUAL(result[4], 2); + + // Test with more pairs, and two pairs overlapping 2 percentiles. + total_weight = 100; + CAmount result2[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 }; + feerates.clear(); + + feerates.emplace_back(std::make_pair(1, 9)); + feerates.emplace_back(std::make_pair(2 , 16)); //10th + 25th percentile + feerates.emplace_back(std::make_pair(4 ,50)); //50th + 75th percentile + feerates.emplace_back(std::make_pair(5 ,10)); + feerates.emplace_back(std::make_pair(9 ,15)); // 90th percentile + + CalculatePercentilesByWeight(result2, feerates, total_weight); + + BOOST_CHECK_EQUAL(result2[0], 2); + BOOST_CHECK_EQUAL(result2[1], 2); + BOOST_CHECK_EQUAL(result2[2], 4); + BOOST_CHECK_EQUAL(result2[3], 4); + BOOST_CHECK_EQUAL(result2[4], 9); + + // Same test as above, but one of the percentile-overlapping pairs is split in 2. + total_weight = 100; + CAmount result3[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 }; + feerates.clear(); + + feerates.emplace_back(std::make_pair(1, 9)); + feerates.emplace_back(std::make_pair(2 , 11)); // 10th percentile + feerates.emplace_back(std::make_pair(2 , 5)); // 25th percentile + feerates.emplace_back(std::make_pair(4 ,50)); //50th + 75th percentile + feerates.emplace_back(std::make_pair(5 ,10)); + feerates.emplace_back(std::make_pair(9 ,15)); // 90th percentile + + CalculatePercentilesByWeight(result3, feerates, total_weight); + + BOOST_CHECK_EQUAL(result3[0], 2); + BOOST_CHECK_EQUAL(result3[1], 2); + BOOST_CHECK_EQUAL(result3[2], 4); + BOOST_CHECK_EQUAL(result3[3], 4); + BOOST_CHECK_EQUAL(result3[4], 9); + + // Test with one transaction spanning all percentiles. + total_weight = 104; + CAmount result4[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 }; + feerates.clear(); + + feerates.emplace_back(std::make_pair(1, 100)); + feerates.emplace_back(std::make_pair(2, 1)); + feerates.emplace_back(std::make_pair(3, 1)); + feerates.emplace_back(std::make_pair(3, 1)); + feerates.emplace_back(std::make_pair(999999, 1)); + + CalculatePercentilesByWeight(result4, feerates, total_weight); + + for (int64_t i = 0; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) { + BOOST_CHECK_EQUAL(result4[i], 1); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/functional/data/rpc_getblockstats.json b/test/functional/data/rpc_getblockstats.json index 750abc5c42..b8cabe1e5e 100644 --- a/test/functional/data/rpc_getblockstats.json +++ b/test/functional/data/rpc_getblockstats.json @@ -112,13 +112,19 @@ "avgfeerate": 0, "avgtxsize": 0, "blockhash": "1d7fe80f19d28b8e712af0399ac84006db753441f3033111b3a8d610afab364f", + "feerate_percentiles": [ + 0, + 0, + 0, + 0, + 0 + ], "height": 101, "ins": 0, "maxfee": 0, "maxfeerate": 0, "maxtxsize": 0, "medianfee": 0, - "medianfeerate": 0, "mediantime": 1525107242, "mediantxsize": 0, "minfee": 0, @@ -144,12 +150,18 @@ "avgtxsize": 187, "blockhash": "4e21a43675d7a41cb6b944e068c5bcd0a677baf658d9ebe021ae2d2f99397ccc", "height": 102, + "feerate_percentiles": [ + 20, + 20, + 20, + 20, + 20 + ], "ins": 1, "maxfee": 3760, "maxfeerate": 20, "maxtxsize": 187, "medianfee": 3760, - "medianfeerate": 20, "mediantime": 1525107242, "mediantxsize": 187, "minfee": 3760, @@ -174,13 +186,19 @@ "avgfeerate": 109, "avgtxsize": 228, "blockhash": "22d9b8b9c2a37c81515f3fc84f7241f6c07dbcea85ef16b00bcc33ae400a030f", + "feerate_percentiles": [ + 20, + 20, + 20, + 300, + 300 + ], "height": 103, "ins": 3, "maxfee": 49800, "maxfeerate": 300, "maxtxsize": 248, "medianfee": 3760, - "medianfeerate": 20, "mediantime": 1525107243, "mediantxsize": 248, "minfee": 3320, diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index 56a79738a7..8460291831 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -67,7 +67,7 @@ class BIP65Test(BitcoinTestFramework): self.log.info("Test that an invalid-according-to-CLTV transaction can still appear in a block") spendtx = create_transaction(self.nodes[0], self.coinbase_txids[0], - self.nodeaddress, 1.0) + self.nodeaddress, amount=1.0) cltv_invalidate(spendtx) spendtx.rehash() @@ -102,7 +102,7 @@ class BIP65Test(BitcoinTestFramework): block.nVersion = 4 spendtx = create_transaction(self.nodes[0], self.coinbase_txids[1], - self.nodeaddress, 1.0) + self.nodeaddress, amount=1.0) cltv_invalidate(spendtx) spendtx.rehash() diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py index 2d17c3d8c4..af14feb471 100755 --- a/test/functional/feature_csv_activation.py +++ b/test/functional/feature_csv_activation.py @@ -94,15 +94,14 @@ def sign_transaction(node, unsignedtx): return tx def create_bip112special(node, input, txversion, address): - tx = create_transaction(node, input, address, Decimal("49.98")) + tx = create_transaction(node, input, address, amount=Decimal("49.98")) tx.nVersion = txversion signtx = sign_transaction(node, tx) signtx.vin[0].scriptSig = CScript([-1, OP_CHECKSEQUENCEVERIFY, OP_DROP] + list(CScript(signtx.vin[0].scriptSig))) return signtx def send_generic_input_tx(node, coinbases, address): - amount = Decimal("49.99") - return node.sendrawtransaction(ToHex(sign_transaction(node, create_transaction(node, node.getblock(coinbases.pop())['tx'][0], address, amount)))) + return node.sendrawtransaction(ToHex(sign_transaction(node, create_transaction(node, node.getblock(coinbases.pop())['tx'][0], address, amount=Decimal("49.99"))))) def create_bip68txs(node, bip68inputs, txversion, address, locktime_delta=0): """Returns a list of bip68 transactions with different bits set.""" @@ -110,7 +109,7 @@ def create_bip68txs(node, bip68inputs, txversion, address, locktime_delta=0): assert(len(bip68inputs) >= 16) for i, (sdf, srhb, stf, srlb) in enumerate(product(*[[True, False]] * 4)): locktime = relative_locktime(sdf, srhb, stf, srlb) - tx = create_transaction(node, bip68inputs[i], address, Decimal("49.98")) + tx = create_transaction(node, bip68inputs[i], address, amount=Decimal("49.98")) tx.nVersion = txversion tx.vin[0].nSequence = locktime + locktime_delta tx = sign_transaction(node, tx) @@ -125,7 +124,7 @@ def create_bip112txs(node, bip112inputs, varyOP_CSV, txversion, address, locktim assert(len(bip112inputs) >= 16) for i, (sdf, srhb, stf, srlb) in enumerate(product(*[[True, False]] * 4)): locktime = relative_locktime(sdf, srhb, stf, srlb) - tx = create_transaction(node, bip112inputs[i], address, Decimal("49.98")) + tx = create_transaction(node, bip112inputs[i], address, amount=Decimal("49.98")) if (varyOP_CSV): # if varying OP_CSV, nSequence is fixed tx.vin[0].nSequence = BASE_RELATIVE_LOCKTIME + locktime_delta else: # vary nSequence instead, OP_CSV is fixed @@ -269,10 +268,10 @@ class BIP68_112_113Test(BitcoinTestFramework): # Test both version 1 and version 2 transactions for all tests # BIP113 test transaction will be modified before each use to put in appropriate block time - bip113tx_v1 = create_transaction(self.nodes[0], bip113input, self.nodeaddress, Decimal("49.98")) + bip113tx_v1 = create_transaction(self.nodes[0], bip113input, self.nodeaddress, amount=Decimal("49.98")) bip113tx_v1.vin[0].nSequence = 0xFFFFFFFE bip113tx_v1.nVersion = 1 - bip113tx_v2 = create_transaction(self.nodes[0], bip113input, self.nodeaddress, Decimal("49.98")) + bip113tx_v2 = create_transaction(self.nodes[0], bip113input, self.nodeaddress, amount=Decimal("49.98")) bip113tx_v2.vin[0].nSequence = 0xFFFFFFFE bip113tx_v2.nVersion = 2 diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index 8cbdeadecf..53e94c436a 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -55,7 +55,7 @@ class BIP66Test(BitcoinTestFramework): self.log.info("Test that a transaction with non-DER signature can still appear in a block") spendtx = create_transaction(self.nodes[0], self.coinbase_txids[0], - self.nodeaddress, 1.0) + self.nodeaddress, amount=1.0) unDERify(spendtx) spendtx.rehash() @@ -92,7 +92,7 @@ class BIP66Test(BitcoinTestFramework): block.nVersion = 3 spendtx = create_transaction(self.nodes[0], self.coinbase_txids[1], - self.nodeaddress, 1.0) + self.nodeaddress, amount=1.0) unDERify(spendtx) spendtx.rehash() @@ -128,8 +128,7 @@ class BIP66Test(BitcoinTestFramework): assert b'Non-canonical DER signature' in self.nodes[0].p2p.last_message["reject"].reason self.log.info("Test that a version 3 block with a DERSIG-compliant transaction is accepted") - block.vtx[1] = create_transaction(self.nodes[0], - self.coinbase_txids[1], self.nodeaddress, 1.0) + block.vtx[1] = create_transaction(self.nodes[0], self.coinbase_txids[1], self.nodeaddress, amount=1.0) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() block.solve() diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py index ab03871849..9d1805ec2d 100755 --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -61,16 +61,16 @@ class NULLDUMMYTest(BitcoinTestFramework): self.lastblocktime = int(time.time()) + 429 self.log.info("Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [430]") - test1txs = [create_transaction(self.nodes[0], coinbase_txid[0], self.ms_address, 49)] + test1txs = [create_transaction(self.nodes[0], coinbase_txid[0], self.ms_address, amount=49)] txid1 = self.nodes[0].sendrawtransaction(bytes_to_hex_str(test1txs[0].serialize_with_witness()), True) - test1txs.append(create_transaction(self.nodes[0], txid1, self.ms_address, 48)) + test1txs.append(create_transaction(self.nodes[0], txid1, self.ms_address, amount=48)) txid2 = self.nodes[0].sendrawtransaction(bytes_to_hex_str(test1txs[1].serialize_with_witness()), True) - test1txs.append(create_transaction(self.nodes[0], coinbase_txid[1], self.wit_ms_address, 49)) + test1txs.append(create_transaction(self.nodes[0], coinbase_txid[1], self.wit_ms_address, amount=49)) txid3 = self.nodes[0].sendrawtransaction(bytes_to_hex_str(test1txs[2].serialize_with_witness()), True) self.block_submit(self.nodes[0], test1txs, False, True) self.log.info("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation") - test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, 47) + test2tx = create_transaction(self.nodes[0], txid2, self.ms_address, amount=47) trueDummy(test2tx) assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, bytes_to_hex_str(test2tx.serialize_with_witness()), True) @@ -78,14 +78,14 @@ class NULLDUMMYTest(BitcoinTestFramework): self.block_submit(self.nodes[0], [test2tx], False, True) self.log.info("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation") - test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, 46) + test4tx = create_transaction(self.nodes[0], test2tx.hash, self.address, amount=46) test6txs=[CTransaction(test4tx)] trueDummy(test4tx) assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, bytes_to_hex_str(test4tx.serialize_with_witness()), True) self.block_submit(self.nodes[0], [test4tx]) self.log.info("Test 5: Non-NULLDUMMY P2WSH multisig transaction invalid after activation") - test5tx = create_transaction(self.nodes[0], txid3, self.wit_address, 48) + test5tx = create_transaction(self.nodes[0], txid3, self.wit_address, amount=48) test6txs.append(CTransaction(test5tx)) test5tx.wit.vtxinwit[0].scriptWitness.stack[0] = b'\x01' assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, bytes_to_hex_str(test5tx.serialize_with_witness()), True) diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py index c7f6cef61d..faa00d56cf 100755 --- a/test/functional/mempool_reorg.py +++ b/test/functional/mempool_reorg.py @@ -39,9 +39,9 @@ class MempoolCoinbaseTest(BitcoinTestFramework): # and make sure the mempool code behaves correctly. b = [ self.nodes[0].getblockhash(n) for n in range(101, 105) ] coinbase_txids = [ self.nodes[0].getblock(h)['tx'][0] for h in b ] - spend_101_raw = create_raw_transaction(self.nodes[0], coinbase_txids[1], node1_address, 49.99) - spend_102_raw = create_raw_transaction(self.nodes[0], coinbase_txids[2], node0_address, 49.99) - spend_103_raw = create_raw_transaction(self.nodes[0], coinbase_txids[3], node0_address, 49.99) + spend_101_raw = create_raw_transaction(self.nodes[0], coinbase_txids[1], node1_address, amount=49.99) + spend_102_raw = create_raw_transaction(self.nodes[0], coinbase_txids[2], node0_address, amount=49.99) + spend_103_raw = create_raw_transaction(self.nodes[0], coinbase_txids[3], node0_address, amount=49.99) # Create a transaction which is time-locked to two blocks in the future timelock_tx = self.nodes[0].createrawtransaction([{"txid": coinbase_txids[0], "vout": 0}], {node0_address: 49.99}) @@ -57,11 +57,11 @@ class MempoolCoinbaseTest(BitcoinTestFramework): spend_103_id = self.nodes[0].sendrawtransaction(spend_103_raw) self.nodes[0].generate(1) # Time-locked transaction is still too immature to spend - assert_raises_rpc_error(-26,'non-final', self.nodes[0].sendrawtransaction, timelock_tx) + assert_raises_rpc_error(-26, 'non-final', self.nodes[0].sendrawtransaction, timelock_tx) # Create 102_1 and 103_1: - spend_102_1_raw = create_raw_transaction(self.nodes[0], spend_102_id, node1_address, 49.98) - spend_103_1_raw = create_raw_transaction(self.nodes[0], spend_103_id, node1_address, 49.98) + spend_102_1_raw = create_raw_transaction(self.nodes[0], spend_102_id, node1_address, amount=49.98) + spend_103_1_raw = create_raw_transaction(self.nodes[0], spend_103_id, node1_address, amount=49.98) # Broadcast and mine 103_1: spend_103_1_id = self.nodes[0].sendrawtransaction(spend_103_1_raw) diff --git a/test/functional/mempool_resurrect.py b/test/functional/mempool_resurrect.py index 5884a4f454..23939f0be5 100755 --- a/test/functional/mempool_resurrect.py +++ b/test/functional/mempool_resurrect.py @@ -25,16 +25,16 @@ class MempoolCoinbaseTest(BitcoinTestFramework): # Mine a new block # ... make sure all the transactions are confirmed again. - b = [ self.nodes[0].getblockhash(n) for n in range(1, 4) ] - coinbase_txids = [ self.nodes[0].getblock(h)['tx'][0] for h in b ] - spends1_raw = [ create_raw_transaction(self.nodes[0], txid, node0_address, 49.99) for txid in coinbase_txids ] - spends1_id = [ self.nodes[0].sendrawtransaction(tx) for tx in spends1_raw ] + b = [self.nodes[0].getblockhash(n) for n in range(1, 4)] + coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b] + spends1_raw = [create_raw_transaction(self.nodes[0], txid, node0_address, amount=49.99) for txid in coinbase_txids] + spends1_id = [self.nodes[0].sendrawtransaction(tx) for tx in spends1_raw] blocks = [] blocks.extend(self.nodes[0].generate(1)) - spends2_raw = [ create_raw_transaction(self.nodes[0], txid, node0_address, 49.98) for txid in spends1_id ] - spends2_id = [ self.nodes[0].sendrawtransaction(tx) for tx in spends2_raw ] + spends2_raw = [create_raw_transaction(self.nodes[0], txid, node0_address, amount=49.98) for txid in spends1_id] + spends2_id = [self.nodes[0].sendrawtransaction(tx) for tx in spends2_raw] blocks.extend(self.nodes[0].generate(1)) diff --git a/test/functional/mempool_spend_coinbase.py b/test/functional/mempool_spend_coinbase.py index 57ae9d697e..ce3bc3b7e0 100755 --- a/test/functional/mempool_spend_coinbase.py +++ b/test/functional/mempool_spend_coinbase.py @@ -29,9 +29,9 @@ class MempoolSpendCoinbaseTest(BitcoinTestFramework): # Coinbase at height chain_height-100+1 ok in mempool, should # get mined. Coinbase at height chain_height-100+2 is # is too immature to spend. - b = [ self.nodes[0].getblockhash(n) for n in range(101, 103) ] - coinbase_txids = [ self.nodes[0].getblock(h)['tx'][0] for h in b ] - spends_raw = [ create_raw_transaction(self.nodes[0], txid, node0_address, 49.99) for txid in coinbase_txids ] + b = [self.nodes[0].getblockhash(n) for n in range(101, 103)] + coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b] + spends_raw = [create_raw_transaction(self.nodes[0], txid, node0_address, amount=49.99) for txid in coinbase_txids] spend_101_id = self.nodes[0].sendrawtransaction(spends_raw[0]) diff --git a/test/functional/rpc_getblockstats.py b/test/functional/rpc_getblockstats.py index af9a544fd7..b24bed6adf 100755 --- a/test/functional/rpc_getblockstats.py +++ b/test/functional/rpc_getblockstats.py @@ -27,7 +27,7 @@ class GetblockstatsTest(BitcoinTestFramework): 'maxfee', 'maxfeerate', 'medianfee', - 'medianfeerate', + 'feerate_percentiles', 'minfee', 'minfeerate', 'totalfee', diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 56f70d9833..987ade4044 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -118,7 +118,7 @@ def create_coinbase(height, pubkey=None): coinbase.calc_sha256() return coinbase -def create_tx_with_script(prevtx, n, script_sig=b"", amount=1, script_pub_key=CScript()): +def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=CScript()): """Return one-input, one-output transaction object spending the prevtx's n-th output with the given amount. @@ -131,26 +131,24 @@ def create_tx_with_script(prevtx, n, script_sig=b"", amount=1, script_pub_key=CS tx.calc_sha256() return tx -def create_transaction(node, txid, to_address, amount): +def create_transaction(node, txid, to_address, *, amount): """ Return signed transaction spending the first output of the input txid. Note that the node must be able to sign for the output that is being spent, and the node must not be running multiple wallets. """ - raw_tx = create_raw_transaction(node, txid, to_address, amount) + raw_tx = create_raw_transaction(node, txid, to_address, amount=amount) tx = CTransaction() tx.deserialize(BytesIO(hex_str_to_bytes(raw_tx))) return tx -def create_raw_transaction(node, txid, to_address, amount): +def create_raw_transaction(node, txid, to_address, *, amount): """ Return raw signed transaction spending the first output of the input txid. Note that the node must be able to sign for the output that is being spent, and the node must not be running multiple wallets. """ - inputs = [{"txid": txid, "vout": 0}] - outputs = {to_address: amount} - rawtx = node.createrawtransaction(inputs, outputs) + rawtx = node.createrawtransaction(inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount}) signresult = node.signrawtransactionwithwallet(rawtx) assert_equal(signresult["complete"], True) return signresult['hex'] diff --git a/test/lint/lint-format-strings.py b/test/lint/lint-format-strings.py index c07dfe317b..60389176c9 100755 --- a/test/lint/lint-format-strings.py +++ b/test/lint/lint-format-strings.py @@ -123,6 +123,32 @@ def parse_function_call_and_arguments(function_name, function_call): ['foo(', '123', ')'] >>> parse_function_call_and_arguments("foo", 'foo("foo")') ['foo(', '"foo"', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf), err);') + ['strprintf(', '"%s (%d)",', ' std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>,wchar_t>().to_bytes(buf),', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo<wchar_t>().to_bytes(buf), err);') + ['strprintf(', '"%s (%d)",', ' foo<wchar_t>().to_bytes(buf),', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo().to_bytes(buf), err);') + ['strprintf(', '"%s (%d)",', ' foo().to_bytes(buf),', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo << 1, err);') + ['strprintf(', '"%s (%d)",', ' foo << 1,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo<bar>() >> 1, err);') + ['strprintf(', '"%s (%d)",', ' foo<bar>() >> 1,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo < 1 ? bar : foobar, err);') + ['strprintf(', '"%s (%d)",', ' foo < 1 ? bar : foobar,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo < 1, err);') + ['strprintf(', '"%s (%d)",', ' foo < 1,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo > 1 ? bar : foobar, err);') + ['strprintf(', '"%s (%d)",', ' foo > 1 ? bar : foobar,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo > 1, err);') + ['strprintf(', '"%s (%d)",', ' foo > 1,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo <= 1, err);') + ['strprintf(', '"%s (%d)",', ' foo <= 1,', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo <= bar<1, 2>(1, 2), err);') + ['strprintf(', '"%s (%d)",', ' foo <= bar<1, 2>(1, 2),', ' err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo>foo<1,2>(1,2)?bar:foobar,err)'); + ['strprintf(', '"%s (%d)",', ' foo>foo<1,2>(1,2)?bar:foobar,', 'err', ')'] + >>> parse_function_call_and_arguments("strprintf", 'strprintf("%s (%d)", foo>foo<1,2>(1,2),err)'); + ['strprintf(', '"%s (%d)",', ' foo>foo<1,2>(1,2),', 'err', ')'] """ assert(type(function_name) is str and type(function_call) is str and function_name) remaining = normalize(escape(function_call)) @@ -131,9 +157,10 @@ def parse_function_call_and_arguments(function_name, function_call): parts = [expected_function_call] remaining = remaining[len(expected_function_call):] open_parentheses = 1 + open_template_arguments = 0 in_string = False parts.append("") - for char in remaining: + for i, char in enumerate(remaining): parts.append(parts.pop() + char) if char == "\"": in_string = not in_string @@ -151,6 +178,15 @@ def parse_function_call_and_arguments(function_name, function_call): parts.append(parts.pop()[:-1]) parts.append(char) break + prev_char = remaining[i - 1] if i - 1 >= 0 else None + next_char = remaining[i + 1] if i + 1 <= len(remaining) - 1 else None + if char == "<" and next_char not in [" ", "<", "="] and prev_char not in [" ", "<"]: + open_template_arguments += 1 + continue + if char == ">" and next_char not in [" ", ">", "="] and prev_char not in [" ", ">"] and open_template_arguments > 0: + open_template_arguments -= 1 + if open_template_arguments > 0: + continue if char == ",": parts.append("") return parts |