-
Notifications
You must be signed in to change notification settings - Fork 0
Add federated block-signing server implementation #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dvep
Are you sure you want to change the base?
Changes from 1 commit
c4d3a12
cdae6ed
a93c459
daf40f3
bf079d1
0107ee1
826ca7f
79a9dcd
b3a0ab9
1da1bbb
bf28ee6
2817b6a
fef2e4e
2722026
c4500a8
910b875
0c5d845
6bb86db
cb96642
45a9dbd
8efa38a
8862190
3c70538
cbae6d7
9c375ba
ba4cc36
8cc00bc
8a98f08
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,18 +4,29 @@ | |
|
|
||
| #include <federation.h> | ||
|
|
||
| #include <chainparams.h> | ||
| #include <logging.h> | ||
| #include <netaddress.h> | ||
| #include <netbase.h> | ||
| #include <net.h> | ||
| #include <node/context.h> | ||
| #include <pubkey.h> | ||
| #include <streams.h> | ||
| #include <sync.h> | ||
| #include <threadinterrupt.h> | ||
| #include <util/strencodings.h> | ||
| #include <util/system.h> | ||
| #ifdef ENABLE_WALLET | ||
| #include <wallet/wallet.h> | ||
| #endif // ENABLE_WALLET | ||
|
|
||
| #include <algorithm> | ||
| #include <chrono> | ||
| #include <memory> | ||
| #include <map> | ||
| #include <set> | ||
| #include <string> | ||
| #include <utility> | ||
| #include <vector> | ||
|
|
||
| //! Pointer to the NodeContext for the process | ||
|
|
@@ -64,13 +75,172 @@ static std::shared_ptr<CWallet> GetWalletForBlockSigning() | |
| } | ||
| #endif // ENABLE_WALLET | ||
|
|
||
| //! All public keys contained within the block signing script. | ||
| static std::vector<CPubKey> 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<CSubNet> g_whitelist; | ||
|
|
||
| struct BlockSigner { | ||
| CPubKey m_pubkey; | ||
| std::set<std::string> netaddrs; | ||
|
maaku marked this conversation as resolved.
Outdated
|
||
| int64_t m_endpoint_last_update_time; | ||
| std::set<NodeId> 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) { } | ||
|
maaku marked this conversation as resolved.
Outdated
|
||
| }; | ||
|
|
||
| //! A record to store configuration information and state for each block signer. | ||
| static std::map<CPubKey, BlockSigner> 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<CWallet> pwallet = GetWalletForBlockSigning(); | ||
| LegacyScriptPubKeyMan* spk_man = pwallet ? pwallet->GetOrCreateLegacyScriptPubKeyMan() : nullptr; | ||
| #endif // ENABLE_WALLET | ||
| CScript::const_iterator pc = script.begin(); | ||
| opcodetype opcode; | ||
| std::vector<unsigned char> 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; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like you could move the above wallet-check into the post-sorted loop below, and set both index and key. You could even move that entire thing into an ENABLED_WALLET ifdef, unless the plan is to somehow have a pubkey but not a privkey for our own key (external HWW signer or something).
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's precisely what I had in mind for production: a pubkey set as a configuration option, with the privkey stored within a secure hardware enclave or attached hardware wallet. I haven't really worked out the low-level details of this though. For example, do we keep the wallet enabled and add the pubkey as a watch-only key? Or do we remove the wallet and replicate the features we need for a smaller exposed attack surface? Honestly I haven't spend more than 5 minutes thinking about that yet. Obviously right now if you try to run this without the wallet enabled it will error out because it can't find any keys it recognizes.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I moved the wallet-check into the post-sorted loop as suggested because I think that's cleaner. Thanks for the suggestion. I'm not marking this as resolved yet though because of the other unresolved aspects. |
||
| 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 '=<pubkey:ip[:port]>, not '=%s''\n", opt_blocksignnode); | ||
|
maaku marked this conversation as resolved.
Outdated
|
||
| ++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<CService> 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; | ||
|
maaku marked this conversation as resolved.
Outdated
|
||
|
|
||
| // 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) | ||
| { | ||
| // 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 | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.