From 149da420e9f6ca83efd471d7a52cd665e6f1cf2e Mon Sep 17 00:00:00 2001
From: stoffu <stoffu@protonmail.ch>
Date: Thu, 14 Jun 2018 11:15:53 +0900
Subject: [PATCH 1/4] db_lmdb: enable batch transactions by default

---
 src/blockchain_db/lmdb/db_lmdb.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index cc1b06ca0..4580573eb 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -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);

From 34cb6b4b703f30be89388f4cdcc5d7b6780ee988 Mon Sep 17 00:00:00 2001
From: victorsintnicolaas <vicsn@users.noreply.github.com>
Date: Thu, 14 Jun 2018 21:11:49 +0200
Subject: [PATCH 2/4] add --regtest and --fixed-difficulty for regression
 testing

on_generateblocks RPC call combines functionality from the on_getblocktemplate and on_submitblock RPC calls to allow rapid block creation. Difficulty is set permanently to 1 for regtest.
Makes use of FAKECHAIN network type, but takes hard fork heights from mainchain
Default reserve_size in generate_blocks RPC call is now 1. If it is 0, the following error occurs 'Failed to calculate offset for'.
Queries hard fork heights info of other network types
---
 src/blockchain_db/berkeleydb/db_bdb.cpp |  5 ++
 src/blockchain_db/berkeleydb/db_bdb.h   |  2 +
 src/blockchain_db/blockchain_db.h       | 14 ++++++
 src/blockchain_db/lmdb/db_lmdb.cpp      | 15 ++++++
 src/blockchain_db/lmdb/db_lmdb.h        |  2 +
 src/cryptonote_basic/hardfork.h         | 15 +++---
 src/cryptonote_core/blockchain.cpp      | 46 +++++++++++++++++-
 src/cryptonote_core/blockchain.h        | 11 ++++-
 src/cryptonote_core/cryptonote_core.cpp | 32 ++++++++++++-
 src/cryptonote_core/cryptonote_core.h   |  2 +
 src/daemon/daemon.cpp                   |  3 +-
 src/daemon/main.cpp                     |  5 +-
 src/p2p/net_node.inl                    |  3 ++
 src/rpc/core_rpc_server.cpp             | 62 +++++++++++++++++++++++++
 src/rpc/core_rpc_server.h               |  2 +
 src/rpc/core_rpc_server_commands_defs.h | 25 ++++++++++
 src/rpc/core_rpc_server_error_codes.h   |  1 +
 tests/unit_tests/hardfork.cpp           |  1 +
 18 files changed, 232 insertions(+), 14 deletions(-)

diff --git a/src/blockchain_db/berkeleydb/db_bdb.cpp b/src/blockchain_db/berkeleydb/db_bdb.cpp
index e1b76ec1e..f827ab7c3 100644
--- a/src/blockchain_db/berkeleydb/db_bdb.cpp
+++ b/src/blockchain_db/berkeleydb/db_bdb.cpp
@@ -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__);
diff --git a/src/blockchain_db/berkeleydb/db_bdb.h b/src/blockchain_db/berkeleydb/db_bdb.h
index cecbba28f..c90d030a2 100644
--- a/src/blockchain_db/berkeleydb/db_bdb.h
+++ b/src/blockchain_db/berkeleydb/db_bdb.h
@@ -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();
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 19ba32340..02077ac84 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -654,6 +654,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
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 300fb6d2f..05af0f5aa 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -1465,6 +1465,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__);
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index 4580573eb..54aa864a6 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -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();
diff --git a/src/cryptonote_basic/hardfork.h b/src/cryptonote_basic/hardfork.h
index ee5ec0596..a63a66976 100644
--- a/src/cryptonote_basic/hardfork.h
+++ b/src/cryptonote_basic/hardfork.h
@@ -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 */
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 54d8fac31..191130953 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -329,7 +329,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);
@@ -351,6 +351,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)
@@ -805,6 +806,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__);
 
   CRITICAL_REGION_LOCAL(m_difficulty_lock);
@@ -1007,6 +1013,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;
@@ -4374,6 +4385,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);
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index 769e608ca..a3cdecbb8 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -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
      *
@@ -1033,6 +1041,7 @@ namespace cryptonote
 
     network_type m_nettype;
     bool m_offline;
+    difficulty_type m_fixed_difficulty;
 
     std::atomic<bool> m_cancel;
 
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index d2796deeb..54c88acbd 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -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"
@@ -252,6 +262,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);
@@ -374,7 +386,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;
     }
@@ -431,6 +444,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;
@@ -508,7 +531,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");
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index 17b5680e5..2d8ae7930 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -61,6 +61,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;
 
   /************************************************************************/
diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp
index 48671f190..ea24e32eb 100644
--- a/src/daemon/daemon.cpp
+++ b/src/daemon/daemon.cpp
@@ -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))
diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp
index 49494e889..711de0736 100644
--- a/src/daemon/main.cpp
+++ b/src/daemon/main.cpp
@@ -162,9 +162,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;
     }
 
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 9b21705ec..de0aa169b 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -382,6 +382,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");
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index aa105567c..c0b1d8891 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -1209,6 +1209,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;
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index 324f219f8..5e62bc4a8 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -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);
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 70e186848..c0b0577c7 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -1149,6 +1149,31 @@ namespace cryptonote
       END_KV_SERIALIZE_MAP()
     };
   };
+
+  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
   {
diff --git a/src/rpc/core_rpc_server_error_codes.h b/src/rpc/core_rpc_server_error_codes.h
index 69caaa6a6..5a754749f 100644
--- a/src/rpc/core_rpc_server_error_codes.h
+++ b/src/rpc/core_rpc_server_error_codes.h
@@ -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
 
 
diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp
index 7c27b9c5d..083a69b3f 100644
--- a/tests/unit_tests/hardfork.cpp
+++ b/tests/unit_tests/hardfork.cpp
@@ -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() { }

From 9e1403e1556c58065bf415a507af06f19f3cfeda Mon Sep 17 00:00:00 2001
From: victorsintnicolaas <vicsn@users.noreply.github.com>
Date: Wed, 13 Jun 2018 22:38:26 +0200
Subject: [PATCH 3/4] update get_info RPC and bump RPC version

---
 src/rpc/core_rpc_server.cpp             | 2 ++
 src/rpc/core_rpc_server_commands_defs.h | 4 +++-
 src/rpc/message_data_structs.h          | 1 +
 src/serialization/json_object.cpp       | 2 ++
 4 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index c0b1d8891..d06636ebf 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -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();
@@ -1626,6 +1627,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();
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index c0b0577c7..19f889887 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -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)
 
@@ -953,6 +953,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;
@@ -982,6 +983,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)
diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h
index 17ae9629f..fc1b2329d 100644
--- a/src/rpc/message_data_structs.h
+++ b/src/rpc/message_data_structs.h
@@ -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;
diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp
index a7fb58ee4..c2467b863 100644
--- a/src/serialization/json_object.cpp
+++ b/src/serialization/json_object.cpp
@@ -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);

From 207b66ecc28833f1cb211d3489df96fc42ebab28 Mon Sep 17 00:00:00 2001
From: victorsintnicolaas <vicsn@users.noreply.github.com>
Date: Fri, 15 Jun 2018 11:57:22 +0200
Subject: [PATCH 4/4] first new functional tests

---
 tests/README.md                               |  14 ++
 tests/functional_tests/blockchain.py          | 103 +++++++++++++++
 tests/functional_tests/speed.py               |  87 +++++++++++++
 .../test_framework/__init__.py                |   0
 .../functional_tests/test_framework/daemon.py | 105 +++++++++++++++
 tests/functional_tests/test_framework/rpc.py  |  49 +++++++
 .../functional_tests/test_framework/wallet.py | 120 ++++++++++++++++++
 7 files changed, 478 insertions(+)
 create mode 100755 tests/functional_tests/blockchain.py
 create mode 100755 tests/functional_tests/speed.py
 create mode 100644 tests/functional_tests/test_framework/__init__.py
 create mode 100644 tests/functional_tests/test_framework/daemon.py
 create mode 100644 tests/functional_tests/test_framework/rpc.py
 create mode 100644 tests/functional_tests/test_framework/wallet.py

diff --git a/tests/README.md b/tests/README.md
index 48a6c41a7..0bf097254 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -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
 
diff --git a/tests/functional_tests/blockchain.py b/tests/functional_tests/blockchain.py
new file mode 100755
index 000000000..983658a7c
--- /dev/null
+++ b/tests/functional_tests/blockchain.py
@@ -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()
diff --git a/tests/functional_tests/speed.py b/tests/functional_tests/speed.py
new file mode 100755
index 000000000..3d2af9a10
--- /dev/null
+++ b/tests/functional_tests/speed.py
@@ -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()
diff --git a/tests/functional_tests/test_framework/__init__.py b/tests/functional_tests/test_framework/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/functional_tests/test_framework/daemon.py b/tests/functional_tests/test_framework/daemon.py
new file mode 100644
index 000000000..f3490b232
--- /dev/null
+++ b/tests/functional_tests/test_framework/daemon.py
@@ -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)
diff --git a/tests/functional_tests/test_framework/rpc.py b/tests/functional_tests/test_framework/rpc.py
new file mode 100644
index 000000000..b21df7b93
--- /dev/null
+++ b/tests/functional_tests/test_framework/rpc.py
@@ -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']
+
+
+
+
diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py
new file mode 100644
index 000000000..357eab5b2
--- /dev/null
+++ b/tests/functional_tests/test_framework/wallet.py
@@ -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)