From c4d3a12e0b94c136bfabf2d406a4e8d29ae8cc8e Mon Sep 17 00:00:00 2001 From: Mark Friedenbach Date: Mon, 12 Jul 2021 17:16:07 -0700 Subject: [PATCH 01/28] [BlockSigner] Add block signer thread initialization code. The block signer has its own thread to manage active roles in the federation protocol. Most state transitions happen in response to messages received over the network, and are therefore processed in the network thread. However the master node for a round is responsible for kicking off the round with a block proposal, and it is the block-signer thread that does that. --- src/Makefile.am | 2 ++ src/federation.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++ src/federation.h | 20 ++++++++++++++++ src/init.cpp | 10 ++++++++ src/logging.cpp | 1 + src/logging.h | 1 + src/threadinterrupt.h | 7 ++++++ 7 files changed, 95 insertions(+) create mode 100644 src/federation.cpp create mode 100644 src/federation.h diff --git a/src/Makefile.am b/src/Makefile.am index a29a52eb0d..cd26b0f5f0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -143,6 +143,7 @@ BITCOIN_CORE_H = \ cuckoocache.h \ dbwrapper.h \ dynafed.h \ + federation.h \ flatfile.h \ fs.h \ httprpc.h \ @@ -310,6 +311,7 @@ libbitcoin_server_a_SOURCES = \ consensus/tx_verify.cpp \ dynafed.cpp \ dbwrapper.cpp \ + federation.cpp \ flatfile.cpp \ httprpc.cpp \ httpserver.cpp \ diff --git a/src/federation.cpp b/src/federation.cpp new file mode 100644 index 0000000000..ada898b618 --- /dev/null +++ b/src/federation.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2021 Digital Garage +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include + +#include + +//! Pointer to the NodeContext for the process +static NodeContext* g_context = nullptr; + +//! Critical section guarding access to any of the block-signing federation global state +static RecursiveMutex cs_block_signer; + +//! Thread interrupter which is used to command the block-signing thread to terminate. +static CThreadInterrupt g_thread_interrupt; +void InterruptBlockSignerThread() { + g_thread_interrupt(); +} + +/** Manages connections to other nodes in the block-signing federation, and + ** performs this node's role in the block-signing protocol. */ +void ThreadBlockSigner(NodeContext& node) +{ + // Make node context accessible everywhere from within this file. + g_context = &node; + + // Blocks are generated on the minute, every minute. We round the current + // time up to the next minute to get the initial block generation time. + // (Note that the following code truncates back to the last minute interval + // start time, but will then be advanced past the present at the beginning + // of the mining loop below.) + std::chrono::steady_clock::time_point nextblocktime( + std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch())); + + while (true) { + // Sleep this thread until the start of the next block interval. + nextblocktime += std::chrono::minutes{1}; + if (!g_thread_interrupt.sleep_until(nextblocktime)) { + // Thread interrupted. Shutdown in progress? + break; + } + + LogPrint(BCLog::FEDERATION, "A real block signer would generate a block here.\n"); + } +} + +// End of File diff --git a/src/federation.h b/src/federation.h new file mode 100644 index 0000000000..b5d096ed8e --- /dev/null +++ b/src/federation.h @@ -0,0 +1,20 @@ +// Copyright (c) 2021 Digital Garage +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_FEDERATION_H +#define BITCOIN_FEDERATION_H + +#include + +/** Run an instance of the block-signing protocol manager. */ +void ThreadBlockSigner(NodeContext &node); + +// For some reason the Boost thread interrupt is not interrupting the sleep +// within ThreadBlockSigner, so we provide a special-purpose interruption +// routine. This should be investigated and removed if possible. +void InterruptBlockSignerThread(); + +#endif // BITCOIN_FEDERATION_H + +// End of File diff --git a/src/init.cpp b/src/init.cpp index 22d09d28e9..d6c34ee92d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,7 @@ static bool fFeeEstimatesInitialized = false; static const bool DEFAULT_PROXYRANDOMIZE = true; static const bool DEFAULT_REST_ENABLE = false; static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false; +static const bool DEFAULT_BLOCK_SIGNER_ENABLE = false; #ifdef WIN32 // Win32 LevelDB doesn't use filedescriptors, and the ones used for @@ -174,6 +176,7 @@ void Interrupt(NodeContext& node) InterruptREST(); InterruptTorControl(); InterruptMapPort(); + InterruptBlockSignerThread(); if (node.connman) node.connman->Interrupt(); if (g_txindex) { @@ -639,6 +642,8 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-ct_bits", strprintf("The default number of hiding bits in a rangeproof. Will be exceeded to cover amounts exceeding the maximum hiding value. (default: %d)", 52), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-ct_exponent", strprintf("The hiding exponent. (default: %s)", 0), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-blocksigner", strprintf("Enable built-in block-signing federated peer protocol. (default: %s)", DEFAULT_BLOCK_SIGNER_ENABLE ? "on" : "off"), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); + // Add the hidden options argsman.AddHiddenArgs(hidden_args); argsman.AddHiddenArgs(elements_hidden_args); @@ -2064,6 +2069,11 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA return false; } + // Start the block-signing protocol management thread + if (args.GetBoolArg("-blocksigner", DEFAULT_BLOCK_SIGNER_ENABLE)) { + threadGroup.create_thread(std::bind(&TraceThread, "block-signer", [&node](){ThreadBlockSigner(node);})); + } + // ********************************************************* Step 13: Check PAK if (chainparams.GetEnforcePak()) { if (!chainparams.GetConsensus().first_extension_space.empty() && diff --git a/src/logging.cpp b/src/logging.cpp index 35e0754f20..fe621454e1 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -155,6 +155,7 @@ const CLogCategoryDesc LogCategories[] = {BCLog::QT, "qt"}, {BCLog::LEVELDB, "leveldb"}, {BCLog::VALIDATION, "validation"}, + {BCLog::FEDERATION, "federation"}, {BCLog::ALL, "1"}, {BCLog::ALL, "all"}, }; diff --git a/src/logging.h b/src/logging.h index 7e646ef67a..d5d47550ff 100644 --- a/src/logging.h +++ b/src/logging.h @@ -56,6 +56,7 @@ namespace BCLog { QT = (1 << 19), LEVELDB = (1 << 20), VALIDATION = (1 << 21), + FEDERATION = (1 << 29), ALL = ~(uint32_t)0, }; diff --git a/src/threadinterrupt.h b/src/threadinterrupt.h index 2665f8a5be..f7a0a29dfc 100644 --- a/src/threadinterrupt.h +++ b/src/threadinterrupt.h @@ -27,6 +27,13 @@ class CThreadInterrupt bool sleep_for(std::chrono::seconds rel_time); bool sleep_for(std::chrono::minutes rel_time); + template + bool sleep_until(std::chrono::time_point timeout) + { + WAIT_LOCK(mut, lock); + return !cond.wait_until(lock, timeout, [this]() { return flag.load(std::memory_order_acquire); }); + } + private: std::condition_variable cond; Mutex mut; From cdae6ed962f3fe77804a59da074e2079d9a86af1 Mon Sep 17 00:00:00 2001 From: Mark Friedenbach Date: Mon, 9 Aug 2021 20:56:37 -0700 Subject: [PATCH 02/28] [BlockSigner] Add utility function for fetching wallet to use for block signing. --- src/federation.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ src/wallet/init.cpp | 2 ++ 2 files changed, 43 insertions(+) diff --git a/src/federation.cpp b/src/federation.cpp index ada898b618..a7ef59e2a4 100644 --- a/src/federation.cpp +++ b/src/federation.cpp @@ -8,8 +8,15 @@ #include #include #include +#include +#ifdef ENABLE_WALLET +#include +#endif // ENABLE_WALLET #include +#include +#include +#include //! Pointer to the NodeContext for the process static NodeContext* g_context = nullptr; @@ -23,6 +30,40 @@ void InterruptBlockSignerThread() { g_thread_interrupt(); } +#ifdef ENABLE_WALLET +// Elements supports multiple user wallets. The wallet which should be used for +// block signing can be specified via a configuration option, otherwise the +// "default" wallet will be used. +static std::shared_ptr GetWalletForBlockSigning() +{ + std::vector> wallets = GetWallets(); + if (wallets.empty()) { + return nullptr; + } + // The user can configure which wallet to use for block signing with the + // '-signblockwallet' option. By default, the default wallet is used. + const std::string requestedwallet = g_context->args->GetArg("-signblockwallet", ""); + std::shared_ptr ret = GetWallet(requestedwallet); + if (ret) { + return ret; + } + // If a wallet name was specified but not found, let's log that so the user + // knows they need to fix their configuration. + if (requestedwallet != "" && requestedwallet != "0") { + LogPrintf("Requested wallet \"%s\" be used to sign block proposals, but no such wallet found.\n", requestedwallet); + } + // The user can disable use of a wallet for block signing by setting + // '-signblockwallet' to 0 or false or '-nosignblockwallet=1'. Note that + // there must not be a wallet named "0" (or "false", etc.), or else that + // wallet will be selected above. + if (!g_context->args->GetBoolArg("-signblockwallet", true)) { + return nullptr; + } + // If we get this far, it is because the default wallet is requested. + return !wallets.empty() ? wallets[0] : nullptr; +} +#endif // ENABLE_WALLET + /** Manages connections to other nodes in the block-signing federation, and ** performs this node's role in the block-signing protocol. */ void ThreadBlockSigner(NodeContext& node) diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 8b2ef191fb..317c49f49e 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -68,6 +68,8 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const #endif argsman.AddArg("-walletrbf", strprintf("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)", DEFAULT_WALLET_RBF), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + argsman.AddArg("-signblockwallet=", "Use the named wallet for signing block proposals in the federation block-signing code", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::ELEMENTS); + argsman.AddArg("-dblogsize=", strprintf("Flush wallet database activity from memory to disk log every megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); argsman.AddArg("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db environment (default: %u)", DEFAULT_WALLET_PRIVDB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); From a93c4594b2101e600285e893f584edf057bfcf5e Mon Sep 17 00:00:00 2001 From: Mark Friedenbach Date: Mon, 9 Aug 2021 12:00:21 -0700 Subject: [PATCH 03/28] [BlockSigner] Parse federation and block signer configuration options. --- src/federation.cpp | 170 +++++++++++++++++++++++++++++++++++++++++++++ src/init.cpp | 1 + 2 files changed, 171 insertions(+) diff --git a/src/federation.cpp b/src/federation.cpp index a7ef59e2a4..4a2748dd14 100644 --- a/src/federation.cpp +++ b/src/federation.cpp @@ -4,18 +4,29 @@ #include +#include #include +#include +#include +#include #include +#include +#include #include #include +#include #include #ifdef ENABLE_WALLET #include #endif // ENABLE_WALLET +#include #include #include +#include +#include #include +#include #include //! Pointer to the NodeContext for the process @@ -64,6 +75,158 @@ static std::shared_ptr GetWalletForBlockSigning() } #endif // ENABLE_WALLET +//! All public keys contained within the block signing script. +static std::vector g_block_signing_keys; +//! The block signing public key associated with this node. +static CPubKey g_our_block_signing_key; +//! The index of the block signing key associated with this node. +static int g_our_block_signing_key_index = 0; + +//! The subnets match only the IPs which are known federation block-signing +//! nodes. Block-signing messages from other nodes not matching any of these +//! subnets are ignored, so as to not reveal that we are a block-signing +//! federation node. +static std::set g_whitelist; + +struct BlockSigner { + CPubKey m_pubkey; + std::set netaddrs; + int64_t m_endpoint_last_update_time; + std::set m_nodes; + + BlockSigner() : m_endpoint_last_update_time(0) { } + BlockSigner(const CPubKey& pubkey, const std::string& netaddr) : m_pubkey(pubkey), netaddrs({netaddr}), m_endpoint_last_update_time(0) { } +}; + +//! A record to store configuration information and state for each block signer. +static std::map g_block_signers; + +// Parses the configuration options to determine connection and pubkey +// information for all the other federation peers. +static bool ReadFederationOpts() { + using std::swap; + + LOCK(cs_block_signer); + + CScript script = Params().GetConsensus().signblockscript; +#if ENABLE_WALLET + std::shared_ptr pwallet = GetWalletForBlockSigning(); + LegacyScriptPubKeyMan* spk_man = pwallet ? pwallet->GetOrCreateLegacyScriptPubKeyMan() : nullptr; +#endif // ENABLE_WALLET + CScript::const_iterator pc = script.begin(); + opcodetype opcode; + std::vector data; + while (pc < script.end() && script.GetOp(pc, opcode, data)) { + // We are only interested in data pushes + if (data.empty()) { + continue; + } + // Check that the push data is a valid pubkey + CPubKey pk(data); + if (!pk.IsFullyValid()) { + continue; + } + // Treat any valid pubkey as a block-signing key + g_block_signing_keys.push_back(pk); +#if ENABLE_WALLET + // And check to see if it is *our* block-signing key + if (spk_man && spk_man->HaveKey(pk.GetID())) { + g_our_block_signing_key = pk; + } +#endif // ENABLE_WALLET + } + + if (!g_our_block_signing_key.IsValid()) { + LogPrintf("Error: unable to determine block-signing key.\n"); + return false; + } + + std::sort(g_block_signing_keys.begin(), + g_block_signing_keys.end()); + + g_our_block_signing_key_index = 0; + for (const auto& key : g_block_signing_keys) { + if (key == g_our_block_signing_key) { + break; + } + ++g_our_block_signing_key_index; + } + if (g_our_block_signing_key_index >= g_block_signing_keys.size()) { + // log error + return false; + } + + std::string pkstr(HexStr(CDataStream(SER_NETWORK, CLIENT_VERSION) << g_our_block_signing_key), 2); + LogPrint(BCLog::FEDERATION, "Identified block-signing key to be %s\n", pkstr); + + int errors = 0; + const auto& opt_blocksignnodes = g_context->args->GetArgs("-blocksignnode"); + for (const auto& opt_blocksignnode : opt_blocksignnodes) { + auto pos = opt_blocksignnode.find(':'); + if (pos == std::string::npos) { + LogPrintf("Error: -blocksignnode configuration must be in the form '=, not '=%s''\n", opt_blocksignnode); + ++errors; + continue; + } + + CPubKey pk; + std::string substr(opt_blocksignnode, 0, pos); + if (IsHex(substr) && substr.size() == 2*33) { + auto data = ParseHex(substr); + pk.Set(data.begin(), data.end()); + if (!pk.IsFullyValid()) { + LogPrintf("Error: invalid key provided to -blocksignnode: %s\n", substr); + ++errors; + continue; + } + } else { + LogPrintf("Error: \"%s\" is not a 33-byte hex-encoded public key\n", substr); + ++errors; + continue; + } + + std::string netaddr(opt_blocksignnode, pos + 1); + if (netaddr.empty()) { + LogPrintf("Error: no network address is provided for -blocksignnode=%s\n", opt_blocksignnode); + ++errors; + continue; + } + + std::vector endpoints; + int64_t time = GetTime(); + if (!Lookup(netaddr.c_str(), endpoints, Params().GetDefaultPort(), fNameLookup && !HaveNameProxy(), 256)) { + LogPrintf("Error: unable to resolve network address for -blocksignnode=%s\n", opt_blocksignnode); + ++errors; + continue; + } + + // We were able to resolve the network address into at least one ip:port + // service endpoint, so we now assume the address is well-formed. + LogPrint(BCLog::FEDERATION, "Mapping block signer %s to network address %s\n", pkstr, netaddr); + auto itr = g_block_signers.find(pk); + if (itr != g_block_signers.end()) { + itr->second.netaddrs.insert(netaddr); + } else { + g_block_signers[pk] = BlockSigner(pk, netaddr); + itr = g_block_signers.find(pk); + } + itr->second.m_endpoint_last_update_time = time; + + // For each service endpoint, we let the connection manager know about + // the node, and add it to our own list of potential peers. + for (const auto& endpoint : endpoints) { + LogPrint(BCLog::FEDERATION, "Whitelisting resolved endpoint %s for block signer %s\n", endpoint.ToString(), pkstr); + g_whitelist.emplace(endpoint); + } + + // Add the network address to the connection manager, so that a p2p + // connection is attempted. + g_context->connman->AddNode(netaddr); + } + + return !errors; +} + /** Manages connections to other nodes in the block-signing federation, and ** performs this node's role in the block-signing protocol. */ void ThreadBlockSigner(NodeContext& node) @@ -71,6 +234,13 @@ void ThreadBlockSigner(NodeContext& node) // Make node context accessible everywhere from within this file. g_context = &node; + // Parse the various federation and block signing configuration + // options and initialize the global variable state accordingly. + if (!ReadFederationOpts()) { + LogPrintf("Error processing federation command-line options. Block-signing services will be disabled.\n"); + return; + } + // Blocks are generated on the minute, every minute. We round the current // time up to the next minute to get the initial block generation time. // (Note that the following code truncates back to the last minute interval diff --git a/src/init.cpp b/src/init.cpp index d6c34ee92d..f5793f2022 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -643,6 +643,7 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-ct_exponent", strprintf("The hiding exponent. (default: %s)", 0), ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-blocksigner", strprintf("Enable built-in block-signing federated peer protocol. (default: %s)", DEFAULT_BLOCK_SIGNER_ENABLE ? "on" : "off"), ArgsManager::ALLOW_ANY, OptionsCategory::ELEMENTS); + argsman.AddArg("-blocksignnode=:", "Connection information for the block-signing federation peer associated with the specified pubkey. May be specified as many times as necessary for different peers and/or alternative connection strings for a given peer.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::ELEMENTS); // Add the hidden options argsman.AddHiddenArgs(hidden_args); From daf40f31b0ce577e83726dc58e61dd8d18bb58fa Mon Sep 17 00:00:00 2001 From: Mark Friedenbach Date: Mon, 2 Aug 2021 20:07:11 -0700 Subject: [PATCH 04/28] [BlockSigner] Use the wallet to generate blocks at fixed intervals. --- src/federation.cpp | 207 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 1 deletion(-) diff --git a/src/federation.cpp b/src/federation.cpp index 4a2748dd14..ba82a25a83 100644 --- a/src/federation.cpp +++ b/src/federation.cpp @@ -4,19 +4,29 @@ #include +#include #include +#include #include +#include #include #include #include #include +#include +#include #include +#include