Merge pull request #3854

149da42 db_lmdb: enable batch transactions by default (stoffu)
34cb6b4 add --regtest and --fixed-difficulty for regression testing (vicsn)
9e1403e update get_info RPC and bump RPC version (vicsn)
207b66e first new functional tests (vicsn)
This commit is contained in:
luigi1111 2018-07-19 13:35:25 -05:00
commit 025187e6c9
No known key found for this signature in database
GPG Key ID: F4ACA0183641E010
27 changed files with 719 additions and 16 deletions

View File

@ -1213,6 +1213,11 @@ std::vector<std::string> BlockchainBDB::get_filenames() const
return full_paths;
}
bool BlockchainBDB::remove_data_file(const std::string& folder)
{
return true;
}
std::string BlockchainBDB::get_db_name() const
{
LOG_PRINT_L3("BlockchainBDB::" << __func__);

View File

@ -244,6 +244,8 @@ public:
virtual std::vector<std::string> get_filenames() const;
virtual bool remove_data_file(const std::string& folder);
virtual std::string get_db_name() const;
virtual bool lock();

View File

@ -655,6 +655,20 @@ public:
*/
virtual std::vector<std::string> get_filenames() const = 0;
/**
* @brief remove file(s) storing the database
*
* This function is for resetting the database (for core tests, functional tests, etc).
* The function reset() is not usable because it needs to open the database file first
* which can fail if the existing database file is in an incompatible format.
* As such, this function needs to be called before calling open().
*
* @param folder The path of the folder containing the database file(s) which must not end with slash '/'.
*
* @return true if the operation is succesfull
*/
virtual bool remove_data_file(const std::string& folder) const = 0;
// return the name of the folder the db's file(s) should reside in
/**
* @brief gets the name of the folder the BlockchainDB's file(s) should be in

View File

@ -1469,6 +1469,21 @@ std::vector<std::string> BlockchainLMDB::get_filenames() const
return filenames;
}
bool BlockchainLMDB::remove_data_file(const std::string& folder) const
{
const std::string filename = folder + "/data.mdb";
try
{
boost::filesystem::remove(filename);
}
catch (const std::exception &e)
{
MERROR("Failed to remove " << filename << ": " << e.what());
return false;
}
return true;
}
std::string BlockchainLMDB::get_db_name() const
{
LOG_PRINT_L3("BlockchainLMDB::" << __func__);

View File

@ -166,7 +166,7 @@ struct mdb_txn_safe
class BlockchainLMDB : public BlockchainDB
{
public:
BlockchainLMDB(bool batch_transactions=false);
BlockchainLMDB(bool batch_transactions=true);
~BlockchainLMDB();
virtual void open(const std::string& filename, const int mdb_flags=0);
@ -181,6 +181,8 @@ public:
virtual std::vector<std::string> get_filenames() const;
virtual bool remove_data_file(const std::string& folder) const;
virtual std::string get_db_name() const;
virtual bool lock();

View File

@ -220,6 +220,14 @@ namespace cryptonote
*/
uint64_t get_window_size() const { return window_size; }
struct Params {
uint8_t version;
uint8_t threshold;
uint64_t height;
time_t time;
Params(uint8_t version, uint64_t height, uint8_t threshold, time_t time): version(version), threshold(threshold), height(height), time(time) {}
};
private:
uint8_t get_block_version(uint64_t height) const;
@ -244,13 +252,6 @@ namespace cryptonote
uint8_t original_version;
uint64_t original_version_till_height;
struct Params {
uint8_t version;
uint8_t threshold;
uint64_t height;
time_t time;
Params(uint8_t version, uint64_t height, uint8_t threshold, time_t time): version(version), threshold(threshold), height(height), time(time) {}
};
std::vector<Params> heights;
std::deque<uint8_t> versions; /* rolling window of the last N blocks' versions */

View File

@ -330,7 +330,7 @@ uint64_t Blockchain::get_current_blockchain_height() const
//------------------------------------------------------------------
//FIXME: possibly move this into the constructor, to avoid accidentally
// dereferencing a null BlockchainDB pointer
bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline, const cryptonote::test_options *test_options)
bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline, const cryptonote::test_options *test_options, difficulty_type fixed_difficulty)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_tx_pool);
@ -352,6 +352,7 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline
m_nettype = test_options != NULL ? FAKECHAIN : nettype;
m_offline = offline;
m_fixed_difficulty = fixed_difficulty;
if (m_hardfork == nullptr)
{
if (m_nettype == FAKECHAIN || m_nettype == STAGENET)
@ -795,6 +796,11 @@ bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk, bool *orph
// less blocks than desired if there aren't enough.
difficulty_type Blockchain::get_difficulty_for_next_block()
{
if (m_fixed_difficulty)
{
return m_db->height() ? m_fixed_difficulty : 1;
}
LOG_PRINT_L3("Blockchain::" << __func__);
crypto::hash top_hash = get_tail_id();
@ -1006,6 +1012,11 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::
// an alternate chain.
difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei) const
{
if (m_fixed_difficulty)
{
return m_db->height() ? m_fixed_difficulty : 1;
}
LOG_PRINT_L3("Blockchain::" << __func__);
std::vector<uint64_t> timestamps;
std::vector<difficulty_type> cumulative_difficulties;
@ -4405,6 +4416,39 @@ HardFork::State Blockchain::get_hard_fork_state() const
return m_hardfork->get_state();
}
const std::vector<HardFork::Params>& Blockchain::get_hard_fork_heights(network_type nettype)
{
static const std::vector<HardFork::Params> mainnet_heights = []()
{
std::vector<HardFork::Params> heights;
for (const auto& i : mainnet_hard_forks)
heights.emplace_back(i.version, i.height, i.threshold, i.time);
return heights;
}();
static const std::vector<HardFork::Params> testnet_heights = []()
{
std::vector<HardFork::Params> heights;
for (const auto& i : testnet_hard_forks)
heights.emplace_back(i.version, i.height, i.threshold, i.time);
return heights;
}();
static const std::vector<HardFork::Params> stagenet_heights = []()
{
std::vector<HardFork::Params> heights;
for (const auto& i : stagenet_hard_forks)
heights.emplace_back(i.version, i.height, i.threshold, i.time);
return heights;
}();
static const std::vector<HardFork::Params> dummy;
switch (nettype)
{
case MAINNET: return mainnet_heights;
case TESTNET: return testnet_heights;
case STAGENET: return stagenet_heights;
default: return dummy;
}
}
bool Blockchain::get_hard_fork_voting_info(uint8_t version, uint32_t &window, uint32_t &votes, uint32_t &threshold, uint64_t &earliest_height, uint8_t &voting) const
{
return m_hardfork->get_voting_info(version, window, votes, threshold, earliest_height, voting);

View File

@ -114,10 +114,11 @@ namespace cryptonote
* @param nettype network type
* @param offline true if running offline, else false
* @param test_options test parameters
* @param fixed_difficulty fixed difficulty for testing purposes; 0 means disabled
*
* @return true on success, false if any initialization steps fail
*/
bool init(BlockchainDB* db, const network_type nettype = MAINNET, bool offline = false, const cryptonote::test_options *test_options = NULL);
bool init(BlockchainDB* db, const network_type nettype = MAINNET, bool offline = false, const cryptonote::test_options *test_options = NULL, difficulty_type fixed_difficulty = 0);
/**
* @brief Initialize the Blockchain state
@ -754,6 +755,13 @@ namespace cryptonote
*/
HardFork::State get_hard_fork_state() const;
/**
* @brief gets the hardfork heights of given network
*
* @return the HardFork object
*/
static const std::vector<HardFork::Params>& get_hard_fork_heights(network_type nettype);
/**
* @brief gets the current hardfork version in use/voted for
*
@ -1040,6 +1048,7 @@ namespace cryptonote
network_type m_nettype;
bool m_offline;
difficulty_type m_fixed_difficulty;
std::atomic<bool> m_cancel;

View File

@ -76,6 +76,16 @@ namespace cryptonote
, "Run on stagenet. The wallet must be launched with --stagenet flag."
, false
};
const command_line::arg_descriptor<bool> arg_regtest_on = {
"regtest"
, "Run in a regression testing mode."
, false
};
const command_line::arg_descriptor<difficulty_type> arg_fixed_difficulty = {
"fixed-difficulty"
, "Fixed difficulty used for testing."
, 0
};
const command_line::arg_descriptor<std::string, false, true, 2> arg_data_dir = {
"data-dir"
, "Specify data directory"
@ -251,6 +261,8 @@ namespace cryptonote
command_line::add_arg(desc, arg_testnet_on);
command_line::add_arg(desc, arg_stagenet_on);
command_line::add_arg(desc, arg_regtest_on);
command_line::add_arg(desc, arg_fixed_difficulty);
command_line::add_arg(desc, arg_dns_checkpoints);
command_line::add_arg(desc, arg_prep_blocks_threads);
command_line::add_arg(desc, arg_fast_block_sync);
@ -373,7 +385,8 @@ namespace cryptonote
{
start_time = std::time(nullptr);
if (test_options != NULL)
const bool regtest = command_line::get_arg(vm, arg_regtest_on);
if (test_options != NULL || regtest)
{
m_nettype = FAKECHAIN;
}
@ -430,6 +443,16 @@ namespace cryptonote
blockchain_db_sync_mode sync_mode = db_defaultsync;
uint64_t blocks_per_sync = 1;
if (m_nettype == FAKECHAIN)
{
// reset the db by removing the database file before opening it
if (!db->remove_data_file(filename))
{
MERROR("Failed to remove data file in " << filename);
return false;
}
}
try
{
uint64_t db_flags = 0;
@ -507,7 +530,12 @@ namespace cryptonote
m_blockchain_storage.set_user_options(blocks_threads,
blocks_per_sync, sync_mode, fast_sync);
r = m_blockchain_storage.init(db.release(), m_nettype, m_offline, test_options);
const std::pair<uint8_t, uint64_t> regtest_hard_forks[3] = {std::make_pair(1, 0), std::make_pair(Blockchain::get_hard_fork_heights(MAINNET).back().version, 1), std::make_pair(0, 0)};
const cryptonote::test_options regtest_test_options = {
regtest_hard_forks
};
const difficulty_type fixed_difficulty = command_line::get_arg(vm, arg_fixed_difficulty);
r = m_blockchain_storage.init(db.release(), m_nettype, m_offline, regtest ? &regtest_test_options : test_options, fixed_difficulty);
r = m_mempool.init(max_txpool_size);
CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool");

View File

@ -60,6 +60,8 @@ namespace cryptonote
extern const command_line::arg_descriptor<std::string, false, true, 2> arg_data_dir;
extern const command_line::arg_descriptor<bool, false> arg_testnet_on;
extern const command_line::arg_descriptor<bool, false> arg_stagenet_on;
extern const command_line::arg_descriptor<bool, false> arg_regtest_on;
extern const command_line::arg_descriptor<difficulty_type> arg_fixed_difficulty;
extern const command_line::arg_descriptor<bool> arg_offline;
/************************************************************************/

View File

@ -77,9 +77,10 @@ public:
const auto testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
const auto stagenet = command_line::get_arg(vm, cryptonote::arg_stagenet_on);
const auto regtest = command_line::get_arg(vm, cryptonote::arg_regtest_on);
const auto restricted = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_restricted_rpc);
const auto main_rpc_port = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_rpc_bind_port);
rpcs.emplace_back(new t_rpc{vm, core, p2p, restricted, testnet ? cryptonote::TESTNET : stagenet ? cryptonote::STAGENET : cryptonote::MAINNET, main_rpc_port, "core"});
rpcs.emplace_back(new t_rpc{vm, core, p2p, restricted, testnet ? cryptonote::TESTNET : stagenet ? cryptonote::STAGENET : regtest ? cryptonote::FAKECHAIN : cryptonote::MAINNET, main_rpc_port, "core"});
auto restricted_rpc_port_arg = cryptonote::core_rpc_server::arg_rpc_restricted_bind_port;
if(!command_line::is_arg_defaulted(vm, restricted_rpc_port_arg))

View File

@ -163,9 +163,10 @@ int main(int argc, char const * argv[])
const bool testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on);
const bool stagenet = command_line::get_arg(vm, cryptonote::arg_stagenet_on);
if (testnet && stagenet)
const bool regtest = command_line::get_arg(vm, cryptonote::arg_regtest_on);
if (testnet + stagenet + regtest > 1)
{
std::cerr << "Can't specify more than one of --tesnet and --stagenet" << ENDL;
std::cerr << "Can't specify more than one of --tesnet and --stagenet and --regtest" << ENDL;
return 1;
}

View File

@ -402,6 +402,9 @@ namespace nodetool
full_addrs.insert("162.210.173.150:38080");
full_addrs.insert("162.210.173.151:38080");
}
else if (nettype == cryptonote::FAKECHAIN)
{
}
else
{
full_addrs.insert("107.152.130.98:18080");

View File

@ -194,6 +194,7 @@ namespace cryptonote
res.mainnet = m_nettype == MAINNET;
res.testnet = m_nettype == TESTNET;
res.stagenet = m_nettype == STAGENET;
res.nettype = m_nettype == MAINNET ? "mainnet" : m_nettype == TESTNET ? "testnet" : m_nettype == STAGENET ? "stagenet" : "fakechain";
res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1);
res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit();
res.block_size_median = m_core.get_blockchain_storage().get_current_cumulative_blocksize_median();
@ -1210,6 +1211,68 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_generateblocks(const COMMAND_RPC_GENERATEBLOCKS::request& req, COMMAND_RPC_GENERATEBLOCKS::response& res, epee::json_rpc::error& error_resp)
{
PERF_TIMER(on_generateblocks);
CHECK_CORE_READY();
res.status = CORE_RPC_STATUS_OK;
if(m_core.get_nettype() != FAKECHAIN)
{
error_resp.code = CORE_RPC_ERROR_CODE_REGTEST_REQUIRED;
error_resp.message = "Regtest required when generating blocks";
return false;
}
COMMAND_RPC_GETBLOCKTEMPLATE::request template_req;
COMMAND_RPC_GETBLOCKTEMPLATE::response template_res;
COMMAND_RPC_SUBMITBLOCK::request submit_req;
COMMAND_RPC_SUBMITBLOCK::response submit_res;
template_req.reserve_size = 1;
template_req.wallet_address = req.wallet_address;
submit_req.push_back(boost::value_initialized<std::string>());
res.height = m_core.get_blockchain_storage().get_current_blockchain_height();
bool r;
for(size_t i = 0; i < req.amount_of_blocks; i++)
{
r = on_getblocktemplate(template_req, template_res, error_resp);
res.status = template_res.status;
if (!r) return false;
blobdata blockblob;
if(!string_tools::parse_hexstr_to_binbuff(template_res.blocktemplate_blob, blockblob))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB;
error_resp.message = "Wrong block blob";
return false;
}
block b = AUTO_VAL_INIT(b);
if(!parse_and_validate_block_from_blob(blockblob, b))
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB;
error_resp.message = "Wrong block blob";
return false;
}
miner::find_nonce_for_given_block(b, template_res.difficulty, template_res.height);
submit_req.front() = string_tools::buff_to_hex_nodelimer(block_to_blob(b));
r = on_submitblock(submit_req, submit_res, error_resp);
res.status = submit_res.status;
if (!r) return false;
res.height = template_res.height;
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
uint64_t core_rpc_server::get_block_reward(const block& blk)
{
uint64_t reward = 0;
@ -1565,6 +1628,7 @@ namespace cryptonote
res.mainnet = m_nettype == MAINNET;
res.testnet = m_nettype == TESTNET;
res.stagenet = m_nettype == STAGENET;
res.nettype = m_nettype == MAINNET ? "mainnet" : m_nettype == TESTNET ? "testnet" : m_nettype == STAGENET ? "stagenet" : "fakechain";
res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1);
res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit();
res.block_size_median = m_core.get_blockchain_storage().get_current_cumulative_blocksize_median();

View File

@ -129,6 +129,7 @@ namespace cryptonote
MAP_JON_RPC_WE("getblocktemplate", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE)
MAP_JON_RPC_WE("submit_block", on_submitblock, COMMAND_RPC_SUBMITBLOCK)
MAP_JON_RPC_WE("submitblock", on_submitblock, COMMAND_RPC_SUBMITBLOCK)
MAP_JON_RPC_WE_IF("generateblocks", on_generateblocks, COMMAND_RPC_GENERATEBLOCKS, !m_restricted)
MAP_JON_RPC_WE("get_last_block_header", on_get_last_block_header, COMMAND_RPC_GET_LAST_BLOCK_HEADER)
MAP_JON_RPC_WE("getlastblockheader", on_get_last_block_header, COMMAND_RPC_GET_LAST_BLOCK_HEADER)
MAP_JON_RPC_WE("get_block_header_by_hash", on_get_block_header_by_hash, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH)
@ -196,6 +197,7 @@ namespace cryptonote
bool on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp);
bool on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp);
bool on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp);
bool on_generateblocks(const COMMAND_RPC_GENERATEBLOCKS::request& req, COMMAND_RPC_GENERATEBLOCKS::response& res, epee::json_rpc::error& error_resp);
bool on_get_last_block_header(const COMMAND_RPC_GET_LAST_BLOCK_HEADER::request& req, COMMAND_RPC_GET_LAST_BLOCK_HEADER::response& res, epee::json_rpc::error& error_resp);
bool on_get_block_header_by_hash(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response& res, epee::json_rpc::error& error_resp);
bool on_get_block_header_by_height(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response& res, epee::json_rpc::error& error_resp);

View File

@ -49,7 +49,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 1
#define CORE_RPC_VERSION_MINOR 20
#define CORE_RPC_VERSION_MINOR 21
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
@ -955,6 +955,7 @@ namespace cryptonote
bool mainnet;
bool testnet;
bool stagenet;
std::string nettype;
std::string top_block_hash;
uint64_t cumulative_difficulty;
uint64_t block_size_limit;
@ -984,6 +985,7 @@ namespace cryptonote
KV_SERIALIZE(mainnet)
KV_SERIALIZE(testnet)
KV_SERIALIZE(stagenet)
KV_SERIALIZE(nettype)
KV_SERIALIZE(top_block_hash)
KV_SERIALIZE(cumulative_difficulty)
KV_SERIALIZE(block_size_limit)
@ -1152,6 +1154,31 @@ namespace cryptonote
};
};
struct COMMAND_RPC_GENERATEBLOCKS
{
struct request
{
uint64_t amount_of_blocks;
std::string wallet_address;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(amount_of_blocks)
KV_SERIALIZE(wallet_address)
END_KV_SERIALIZE_MAP()
};
struct response
{
uint64_t height;
std::string status;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(height)
KV_SERIALIZE(status)
END_KV_SERIALIZE_MAP()
};
};
struct block_header_response
{
uint8_t major_version;

View File

@ -42,5 +42,6 @@
#define CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE -10
#define CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC -11
#define CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS -12
#define CORE_RPC_ERROR_CODE_REGTEST_REQUIRED -13

View File

@ -181,6 +181,7 @@ namespace rpc
bool mainnet;
bool testnet;
bool stagenet;
std::string nettype;
crypto::hash top_block_hash;
uint64_t cumulative_difficulty;
uint64_t block_size_limit;

View File

@ -1175,6 +1175,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::DaemonInfo& in
INSERT_INTO_JSON_OBJECT(val, doc, white_peerlist_size, info.white_peerlist_size);
INSERT_INTO_JSON_OBJECT(val, doc, grey_peerlist_size, info.grey_peerlist_size);
INSERT_INTO_JSON_OBJECT(val, doc, testnet, info.testnet);
INSERT_INTO_JSON_OBJECT(val, doc, nettype, info.nettype);
INSERT_INTO_JSON_OBJECT(val, doc, top_block_hash, info.top_block_hash);
INSERT_INTO_JSON_OBJECT(val, doc, cumulative_difficulty, info.cumulative_difficulty);
INSERT_INTO_JSON_OBJECT(val, doc, block_size_limit, info.block_size_limit);
@ -1200,6 +1201,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::DaemonInfo& inf
GET_FROM_JSON_OBJECT(val, info.white_peerlist_size, white_peerlist_size);
GET_FROM_JSON_OBJECT(val, info.grey_peerlist_size, grey_peerlist_size);
GET_FROM_JSON_OBJECT(val, info.testnet, testnet);
GET_FROM_JSON_OBJECT(val, info.nettype, nettype);
GET_FROM_JSON_OBJECT(val, info.top_block_hash, top_block_hash);
GET_FROM_JSON_OBJECT(val, info.cumulative_difficulty, cumulative_difficulty);
GET_FROM_JSON_OBJECT(val, info.block_size_limit, block_size_limit);

View File

@ -50,6 +50,20 @@ To run the same tests on a release build, replace `debug` with `release`.
# Functional tests
[TODO]
Functional tests are located under the `tests/functional` directory.
First, run a regtest daemon in the offline mode and with a fixed difficulty:
```
monerod --regtest --offline --fixed-difficulty 1
```
Alternatively, you can run multiple daemons and let them connect with each other by using `--add-exclusive-node`. In this case, make sure that the same fixed difficulty is given to all the daemons.
Next, restore a mainnet wallet with the following seed and restore height 0 (the file path doesn't matter):
```
velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted
```
Open the wallet file with `monero-wallet-rpc` with RPC port 18083. Finally, start tests by invoking ./blockchain.py or ./speed.py
# Fuzz tests

View File

@ -0,0 +1,103 @@
#!/usr/bin/env python3
# Copyright (c) 2018 The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Test blockchain RPC calls
Test the following RPCs:
- get_info
- generateblocks
- [TODO: many tests still need to be written]
"""
from test_framework.daemon import Daemon
from test_framework.wallet import Wallet
class BlockchainTest():
def run_test(self):
self._test_get_info()
self._test_hardfork_info()
self._test_generateblocks(5)
def _test_get_info(self):
print('Test get_info')
daemon = Daemon()
res = daemon.get_info()
# difficulty should be set to 1 for this test
assert 'difficulty' in res.keys()
assert res['difficulty'] == 1;
# nettype should not be TESTNET
assert 'testnet' in res.keys()
assert res['testnet'] == False;
# nettype should not be STAGENET
assert 'stagenet' in res.keys()
assert res['stagenet'] == False;
# nettype should be FAKECHAIN
assert 'nettype' in res.keys()
assert res['nettype'] == "fakechain";
# free_space should be > 0
assert 'free_space' in res.keys()
assert res['free_space'] > 0
# height should be greater or equal to 1
assert 'height' in res.keys()
assert res['height'] >= 1
def _test_hardfork_info(self):
print('Test hard_fork_info')
daemon = Daemon()
res = daemon.hard_fork_info()
# hard_fork version should be set at height 1
assert 'earliest_height' in res.keys()
assert res['earliest_height'] == 1;
def _test_generateblocks(self, blocks):
print("Test generating", blocks, 'blocks')
daemon = Daemon()
res = daemon.get_info()
height = res['height']
res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks)
assert res['height'] == height + blocks - 1
if __name__ == '__main__':
BlockchainTest().run_test()

87
tests/functional_tests/speed.py Executable file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env python3
# Copyright (c) 2018 The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Test speed of various procedures
Test the following RPCs:
- generateblocks
- transfer
- [TODO: many tests still need to be written]
"""
import time
from time import sleep
from decimal import Decimal
from test_framework.daemon import Daemon
from test_framework.wallet import Wallet
class SpeedTest():
def set_test_params(self):
self.num_nodes = 1
def run_test(self):
daemon = Daemon()
wallet = Wallet()
destinations = wallet.make_uniform_destinations('44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A',1,3)
self._test_speed_generateblocks(daemon=daemon, blocks=70)
for i in range(1, 10):
while wallet.get_balance()['unlocked_balance'] == 0:
print('Waiting for wallet to refresh...')
sleep(1)
self._test_speed_transfer_split(wallet=wallet)
self._test_speed_generateblocks(daemon=daemon, blocks=10)
def _test_speed_generateblocks(self, daemon, blocks):
print('Test speed of block generation')
start = time.time()
res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks)
# wallet seed: velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted
print('generating ', blocks, 'blocks took: ', time.time() - start, 'seconds')
def _test_speed_transfer_split(self, wallet):
print('Test speed of transfer')
start = time.time()
destinations = wallet.make_uniform_destinations('44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A',1)
res = wallet.transfer_split(destinations)
print('generating tx took: ', time.time() - start, 'seconds')
if __name__ == '__main__':
SpeedTest().run_test()

View File

@ -0,0 +1,105 @@
# Copyright (c) 2018 The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Daemon class to make rpc calls and store state."""
from .rpc import JSONRPC
class Daemon(object):
def __init__(self, protocol='http', host='127.0.0.1', port=18081, path='/json_rpc'):
self.rpc = JSONRPC('{protocol}://{host}:{port}{path}'.format(protocol=protocol, host=host, port=port, path=path))
def getblocktemplate(self, address):
getblocktemplate = {
'method': 'getblocktemplate',
'params': {
'wallet_address': address,
'reserve_size' : 1
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(getblocktemplate)
def submitblock(self, block):
submitblock = {
'method': 'submitblock',
'params': [ block ],
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(submitblock)
def getblock(self, height=0):
getblock = {
'method': 'getblock',
'params': {
'height': height
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(getblock)
def get_connections(self):
get_connections = {
'method': 'get_connections',
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(get_connections)
def get_info(self):
get_info = {
'method': 'get_info',
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(get_info)
def hard_fork_info(self):
hard_fork_info = {
'method': 'hard_fork_info',
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(hard_fork_info)
def generateblocks(self, address, blocks=1):
generateblocks = {
'method': 'generateblocks',
'params': {
'amount_of_blocks' : blocks,
'reserve_size' : 20,
'wallet_address': address
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(generateblocks)

View File

@ -0,0 +1,49 @@
# Copyright (c) 2018 The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import requests
import json
class JSONRPC(object):
def __init__(self, url):
self.url = url
def send_request(self, inputs):
res = requests.post(
self.url,
data=json.dumps(inputs),
headers={'content-type': 'application/json'})
res = res.json()
assert 'error' not in res, res
return res['result']

View File

@ -0,0 +1,120 @@
# Copyright (c) 2018 The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Daemon class to make rpc calls and store state."""
from .rpc import JSONRPC
class Wallet(object):
def __init__(self, protocol='http', host='127.0.0.1', port=18083, path='/json_rpc'):
self.rpc = JSONRPC('{protocol}://{host}:{port}{path}'.format(protocol=protocol, host=host, port=port, path=path))
def make_uniform_destinations(self, address, transfer_amount, transfer_number_of_destinations=1):
destinations = []
for i in range(transfer_number_of_destinations):
destinations.append({"amount":transfer_amount,"address":address})
return destinations
def make_destinations(self, addresses, transfer_amounts):
destinations = []
for i in range(len(addresses)):
destinations.append({'amount':transfer_amounts[i],'address':addresses[i]})
return destinations
def transfer(self, destinations, ringsize=7, payment_id=''):
transfer = {
'method': 'transfer',
'params': {
'destinations': destinations,
'mixin' : ringsize - 1,
'get_tx_key' : True
},
'jsonrpc': '2.0',
'id': '0'
}
if(len(payment_id) > 0):
transfer['params'].update({'payment_id' : payment_id})
return self.rpc.send_request(transfer)
def transfer_split(self, destinations, ringsize=7, payment_id=''):
print(destinations)
transfer = {
"method": "transfer_split",
"params": {
"destinations": destinations,
"mixin" : ringsize - 1,
"get_tx_key" : True,
"new_algorithm" : True
},
"jsonrpc": "2.0",
"id": "0"
}
if(len(payment_id) > 0):
transfer['params'].update({'payment_id' : payment_id})
return self.rpc.send_request(transfer)
def create_wallet(self, index=''):
create_wallet = {
'method': 'create_wallet',
'params': {
'filename': 'testWallet' + index,
'password' : '',
'language' : 'English'
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(create_wallet)
def get_balance(self):
get_balance = {
'method': 'get_balance',
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(get_balance)
def sweep_dust(self):
sweep_dust = {
'method': 'sweep_dust',
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(sweep_dust)
def sweep_all(self, address):
sweep_all = {
'method': 'sweep_all',
'params' : {
'address' : ''
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_request(sweep_all)

View File

@ -50,6 +50,7 @@ public:
virtual void safesyncmode(const bool onoff) {}
virtual void reset() {}
virtual std::vector<std::string> get_filenames() const { return std::vector<std::string>(); }
virtual bool remove_data_file(const std::string& folder) const { return true; }
virtual std::string get_db_name() const { return std::string(); }
virtual bool lock() { return true; }
virtual void unlock() { }