Skip to content
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

Add federated block-signing server implementation #4

Open
wants to merge 28 commits into
base: dvep
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c4d3a12
[BlockSigner] Add block signer thread initialization code.
maaku Jul 13, 2021
cdae6ed
[BlockSigner] Add utility function for fetching wallet to use for blo…
maaku Aug 10, 2021
a93c459
[BlockSigner] Parse federation and block signer configuration options.
maaku Aug 9, 2021
daf40f3
[BlockSigner] Use the wallet to generate blocks at fixed intervals.
maaku Aug 3, 2021
bf079d1
[BlockSigner] Add 'blocksign' p2p message, and handshake protocol.
maaku Aug 10, 2021
0107ee1
[BlockSigner] Add peer-to-peer protocol for sharing block proposals, …
maaku Aug 10, 2021
826ca7f
[BlockSigner] Add script demonstrating how to use the federated block…
maaku Aug 11, 2021
79a9dcd
f '[BlockSigner] Parse federation and block signer configuration opti…
maaku Aug 21, 2021
b3a0ab9
f '[BlockSigner] Parse federation and block signer configuration opti…
maaku Aug 21, 2021
1da1bbb
f '[BlockSigner] Parse federation and block signer configuration opti…
maaku Aug 21, 2021
bf28ee6
f '[BlockSigner] Add script demonstrating how to use the federated bl…
maaku Aug 21, 2021
2817b6a
f '[BlockSigner] Parse federation and block signer configuration opti…
maaku Aug 21, 2021
fef2e4e
f '[BlockSigner] Parse federation and block signer configuration opti…
maaku Aug 21, 2021
2722026
f '[BlockSigner] Use the wallet to generate blocks at fixed intervals.'
maaku Aug 21, 2021
c4500a8
f '[BlockSigner] Use the wallet to generate blocks at fixed intervals.'
maaku Aug 21, 2021
910b875
f '[BlockSigner] Use the wallet to generate blocks at fixed intervals.'
maaku Aug 21, 2021
0c5d845
f '[BlockSigner] Add 'blocksign' p2p message, and handshake protocol.'
maaku Aug 21, 2021
6bb86db
f '[BlockSigner] Add 'blocksign' p2p message, and handshake protocol.'
maaku Aug 21, 2021
cb96642
f '[BlockSigner] Add peer-to-peer protocol for sharing block proposal…
maaku Aug 21, 2021
45a9dbd
f '[BlockSigner] Add 'blocksign' p2p message, and handshake protocol.'
maaku Aug 21, 2021
8efa38a
f '[BlockSigner] Add peer-to-peer protocol for sharing block proposal…
maaku Aug 21, 2021
8862190
f '[BlockSigner] Parse federation and block signer configuration opti…
maaku Aug 21, 2021
3c70538
[Mining] Explicitly record block height as part of CBlockTemplate str…
maaku Nov 16, 2021
cbae6d7
f '[BlockSigner] Use the wallet to generate blocks at fixed intervals.'
maaku Nov 16, 2021
9c375ba
[BlockSigner] Verify and store ACK signature from block proposal.
maaku Dec 6, 2021
ba4cc36
[ElementsRegtest] Add public parameters for block-signer elementsregt…
maaku Dec 6, 2021
8cc00bc
f '[ElementsRegtest] Add public parameters for block-signer elementsr…
maaku Dec 7, 2021
8a98f08
[BlockSigner] Ignore empty block proposals, exept every 15 minutes.
maaku Dec 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions src/federation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
maaku marked this conversation as resolved.
Show resolved Hide resolved

//! 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.
Show resolved Hide resolved
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.
Show resolved Hide resolved
};

//! 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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a93c459

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).

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The 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.
Show resolved Hide resolved
++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.
Show resolved Hide resolved

// 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
Expand Down
1 change: 1 addition & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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=<pubkey>:<ip[:port]>", "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);
Expand Down