/* Full node implementation. Expects the blockchain to be present in "./database/" and initialized using ./initchain */ #include <future> #include <bitcoin/bitcoin.hpp> using namespace bc; using std::placeholders::_1; using std::placeholders::_2; using std::placeholders::_3; void output_to_file(std::ofstream& file, log_level level, const std::string& domain, const std::string& body) { if (body.empty()) return; file << level_repr(level); if (!domain.empty()) file << " [" << domain << "]"; file << ": " << body << std::endl; } void output_cerr_and_file(std::ofstream& file, log_level level, const std::string& domain, const std::string& body) { if (body.empty()) return; std::ostringstream output; output << level_repr(level); if (!domain.empty()) output << " [" << domain << "]"; output << ": " << body; std::cerr << output.str() << std::endl; } class fullnode { public: fullnode(); void start(); // Should only be called from the main thread. // It's an error to join() a thread from inside it. void stop(); private: void handle_start(const std::error_code& ec); // New connection has been started. // Subscribe to new transaction messages from the network. void connection_started(const std::error_code& ec, channel_ptr node); // New transaction message from the network. // Attempt to validate it by storing it in the transaction pool. void recv_tx(const std::error_code& ec, const transaction_type& tx, channel_ptr node); // Result of store operation in transaction pool. void new_unconfirm_valid_tx( const std::error_code& ec, const index_list& unconfirmed, const transaction_type& tx); // Threadpools threadpool net_pool_, disk_pool_, mem_pool_; // Services hosts hosts_; handshake handshake_; network network_; protocol protocol_; leveldb_blockchain chain_; poller poller_; transaction_pool txpool_; // Mac OSX needs the bc:: namespace qualifier to compile. // Other systems should be OK. bc::session session_; }; fullnode::fullnode() // Threadpools and the number of threads they spawn. // 6 threads spawned in total. : net_pool_(1), disk_pool_(4), mem_pool_(1), // Networking related services. hosts_(net_pool_), handshake_(net_pool_), network_(net_pool_), protocol_(net_pool_, hosts_, handshake_, network_), // Blockchain database service. chain_(disk_pool_), // Poll new blocks, and transaction memory pool. poller_(mem_pool_, chain_), txpool_(mem_pool_, chain_), // Session manager service. Convenience wrapper. session_(net_pool_, { handshake_, protocol_, chain_, poller_, txpool_}) { } void fullnode::start() { // Subscribe to new connections. protocol_.subscribe_channel( std::bind(&fullnode::connection_started, this, _1, _2)); // Start blockchain. Must finish before any operations // are performed on the database (or they will fail). std::promise<std::error_code> ec_chain; auto blockchain_started = [&](const std::error_code& ec) { ec_chain.set_value(ec); }; chain_.start("database", blockchain_started); std::error_code ec = ec_chain.get_future().get(); if (ec) { log_error() << "Problem starting blockchain: " << ec.message(); return; } // Start transaction pool txpool_.start(); // Fire off app. auto handle_start = std::bind(&fullnode::handle_start, this, _1); session_.start(handle_start); } void fullnode::stop() { session_.stop([](const std::error_code&) {}); // Stop threadpools. net_pool_.stop(); disk_pool_.stop(); mem_pool_.stop(); // Join threadpools. Wait for them to finish. net_pool_.join(); disk_pool_.join(); mem_pool_.join(); // Safely close blockchain database. chain_.stop(); } void fullnode::handle_start(const std::error_code& ec) { if (ec) log_error() << "fullnode: " << ec.message(); } void fullnode::connection_started(const std::error_code& ec, channel_ptr node) { if (ec) { log_warning() << "Couldn't start connection: " << ec.message(); return; } // Subscribe to transaction messages from this node. node->subscribe_transaction( std::bind(&fullnode::recv_tx, this, _1, _2, node)); // Stay subscribed to new connections. protocol_.subscribe_channel( std::bind(&fullnode::connection_started, this, _1, _2)); } void fullnode::recv_tx(const std::error_code& ec, const transaction_type& tx, channel_ptr node) { if (ec) { log_error() << "Receive transaction: " << ec.message(); return; } // Called when the transaction becomes confirmed in a block. auto handle_confirm = [](const std::error_code& ec) { if (ec) log_error() << "Confirm error: " << ec.message(); }; // Validate the transaction from the network. // Attempt to store in the transaction pool and check the result. txpool_.store(tx, handle_confirm, std::bind(&fullnode::new_unconfirm_valid_tx, this, _1, _2, tx)); // Resubscribe to transaction messages from this node. node->subscribe_transaction( std::bind(&fullnode::recv_tx, this, _1, _2, node)); } void fullnode::new_unconfirm_valid_tx( const std::error_code& ec, const index_list& unconfirmed, const transaction_type& tx) { const hash_digest& tx_hash = hash_transaction(tx); if (ec) { log_error() << "Error storing memory pool transaction " << tx_hash << ": " << ec.message(); } else { auto l = log_info(); l << "Accepted transaction "; if (!unconfirmed.empty()) { l << "(Unconfirmed inputs"; for (auto idx: unconfirmed) l << " " << idx; l << ") "; } l << tx_hash; } } int main() { std::ofstream outfile("debug.log"), errfile("error.log"); log_debug().set_output_function( std::bind(output_to_file, std::ref(outfile), _1, _2, _3)); log_info().set_output_function( std::bind(output_to_file, std::ref(outfile), _1, _2, _3)); log_warning().set_output_function( std::bind(output_to_file, std::ref(errfile), _1, _2, _3)); log_error().set_output_function( std::bind(output_cerr_and_file, std::ref(errfile), _1, _2, _3)); log_fatal().set_output_function( std::bind(output_cerr_and_file, std::ref(errfile), _1, _2, _3)); fullnode app; app.start(); std::cin.get(); app.stop(); return 0; }