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
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
207 changes: 206 additions & 1 deletion src/federation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,29 @@

#include <federation.h>

#include <block_proof.h>
#include <chainparams.h>
#include <consensus/validation.h>
#include <logging.h>
#include <miner.h>
#include <netaddress.h>
#include <netbase.h>
#include <net.h>
#include <node/context.h>
#include <pow.h>
#include <primitives/block.h>
#include <pubkey.h>
#include <script/script.h>
#include <script/standard.h>
#include <streams.h>
#include <sync.h>
#include <threadinterrupt.h>
#include <util/strencodings.h>
#include <util/system.h>
#include <validation.h>
#ifdef ENABLE_WALLET
#include <script/generic.hpp>
#include <script/sign.h>
#include <wallet/wallet.h>
#endif // ENABLE_WALLET

Expand Down Expand Up @@ -227,6 +237,173 @@ static bool ReadFederationOpts() {
return !errors;
}

//! The current block proposal, which is announced by the leader at the
//! beginning of their round.
static CBlock g_block_proposal;
//! A collection of valid signatures for the proposed block of the current round.
static std::map<CPubKey, std::vector<unsigned char>> g_block_sigs;

static bool GenerateBlockProposal()
{
const CChainParams& params = Params();
const Consensus::Params& consensusParams = params.GetConsensus();

// FIXME: Is this mandatory coinbase destination really necessary? I recall
// that this is from the Liquid codebase to ensure that Blockstream
// collected fees on Liquid rather than the functionaries. Is this
// something we want to carry over to our chain? If so, there are
// probably better ways to accomplish fee capture.
maaku marked this conversation as resolved.
Show resolved Hide resolved
CScript fee_destination_script = consensusParams.mandatory_coinbase_destination;
// If no forced fee destination script is specified in the chain parameters,
// then it is not consensus-controlled. For testing purposes, we make it
// anyone-can-spend. Let's change this later when we decide what to do
// about collecting fees.
if (fee_destination_script == CScript()) {
fee_destination_script = CScript() << OP_TRUE;
}

// The required_wait parameter is useful for preventing DoS of the
// federation itself. By only using transactions which have been in the
// mempool for X seconds, we ensure that there is probably sufficient time
// for those transactions to have reached the other federation nodes before
// the start of the round.
std::chrono::seconds required_wait{5}; // FIXME: fetch this from config?

std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(*g_context->mempool, params).CreateNewBlock(fee_destination_script, required_wait));
if (!pblocktemplate.get()) {
LogPrint(BCLog::FEDERATION, "Error generating block template proposal.\n");
return false;
}

CBlock &block = pblocktemplate->block;
maaku marked this conversation as resolved.
Show resolved Hide resolved
{
// IncrementExtraNonce sets coinbase flags and builds the merkle tree
LOCK(cs_main);
unsigned int extra_nonce = 0;
IncrementExtraNonce(&block, g_context->chainman->ActiveTip(), extra_nonce);
}

LOCK(cs_block_signer);

using std::swap;
swap(g_block_proposal, block);
return true;
}

// Verifies that the proposed block is building off the tip and is a valid
// block, then signs a relayable confirmation that we will sign this block and
// accept it if the signing threshold is reached.
static bool AcceptBlockProposal()
{
const CChainParams& params = Params();

LOCK(cs_block_signer);
const CBlock& block = g_block_proposal;

LOCK(cs_main);

// Verify that this isn't a replay of a prior block.
BlockMap::iterator mi = g_context->chainman->BlockIndex().find(block.GetHash());
if (mi != g_context->chainman->BlockIndex().end()) {
LogPrint(BCLog::FEDERATION, "Already have proposed block: %s\n", block.GetHash().GetHex());
return false;

Choose a reason for hiding this comment

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

daf40f3

Could this simply be that we are being given the same block by several federation members? I haven't gotten to the end yet, but if that is the case, we should be sure to not punish peers when this function returns false.

Copy link
Author

Choose a reason for hiding this comment

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

I don't think this particular return false will have any effect. Right now the only time the return value is checked is when the leader of the round checks their own block proposal. And barring hash collisions that should never trigger this code path. We don't use it for punishing peers.

The check is more along the lines of "is this proposal one we're willing to sign?" And a return false just terminates participation in the round.

Copy link
Author

Choose a reason for hiding this comment

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

I modified the other call to AcceptBlockProposal so it checks the return value too. Since returning here (true or false) means we aren't signing the block proposal and the round terminates (for this peer), I think return false is correct. It saves a few cycles in that case.

}

// TestBlockValidity only supports blocks built on the current Tip
CBlockIndex* const pindexPrev = g_context->chainman->ActiveTip();
if (block.hashPrevBlock != pindexPrev->GetBlockHash()) {
LogPrint(BCLog::FEDERATION, "Block proposal was not based on our best chain. (%s != %s)\n", block.hashPrevBlock.GetHex(), g_context->chainman->ActiveTip()->GetBlockHash().GetHex());
return false;
}

// Attempt validation of the block proposal, to make sure it would be
// accepted once it is signed.
BlockValidationState state;
bool res = TestBlockValidity(state, params, block, pindexPrev, false, true);
if (!res || !state.IsValid()) {
LogPrint(BCLog::FEDERATION, "Block proposal %s failed validation: %s (%d)\n", block.GetHash().GetHex(), state.GetRejectReason(), (int)state.GetResult());
return false;
}

return true;
}

static bool SignBlock()
{
LOCK(cs_block_signer);
if (g_block_sigs.count(g_our_block_signing_key)) {
// Already signed.
return true;
}

// Generate the block signature
std::vector<unsigned char> sig;
#ifdef ENABLE_WALLET
const CBlock& block = g_block_proposal;
SignatureData block_sigs;
std::shared_ptr<CWallet> pwallet = GetWalletForBlockSigning();
LegacyScriptPubKeyMan* spk_man = pwallet ? pwallet->GetOrCreateLegacyScriptPubKeyMan() : nullptr;
if (g_signed_blocks && spk_man) {
// Sign the block.
if (block.m_dynafed_params.IsNull()) {
GenericSignScript(*spk_man, block.GetBlockHeader(), block.proof.challenge, block_sigs, SCRIPT_NO_SIGHASH_BYTE /* additional_flags */);
} else {
GenericSignScript(*spk_man, block.GetBlockHeader(), block.m_dynafed_params.m_current.m_signblockscript, block_sigs, SCRIPT_VERIFY_NONE /* additional_flags */);
}
// Extract the signature
if (block_sigs.signatures.count(g_our_block_signing_key.GetID())) {
sig = block_sigs.signatures[g_our_block_signing_key.GetID()].second;
}
}
#endif // ENABLE_WALLET
if (sig.empty()) {
LogPrint(BCLog::FEDERATION, "Block signature not generated. Not sure what to do.\n");
return false;
}

g_block_sigs[g_our_block_signing_key] = sig;
return true;
}

static bool SubmitBlock()
{
LOCK(cs_block_signer);
CBlock& block = g_block_proposal;

SignatureData block_sigs;
for (const auto& item : g_block_sigs) {
const CPubKey& pk = item.first;
const std::vector<unsigned char> sig = item.second;
block_sigs.signatures[pk.GetID()] = std::make_pair(pk, sig);
}

// Finalizes the signatures, has no access to keys
FillableSigningProvider keystore;
if (block.m_dynafed_params.IsNull()) {
SimpleSignatureCreator signature_creator(block.GetHash(), 0);
ProduceSignature(keystore, signature_creator, block.proof.challenge, block_sigs, SCRIPT_NO_SIGHASH_BYTE);
block.proof.solution = block_sigs.scriptSig;
} else {
SimpleSignatureCreator signature_creator(block.GetHash(), SIGHASH_ALL);
ProduceSignature(keystore, signature_creator, block.m_dynafed_params.m_current.m_signblockscript, block_sigs, SCRIPT_VERIFY_NONE);
block.m_signblock_witness = block_sigs.scriptWitness;
}

if (!CheckProof(block, Params().GetConsensus())) {
LogPrint(BCLog::FEDERATION, "Signatures incomplete; not submitting block.\n");
return false;
}

std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
if (!g_context->chainman->ProcessNewBlock(Params(), shared_pblock, true, nullptr)) {
LogPrint(BCLog::FEDERATION, "ProccessNewBlock failed for signe block.\n");
maaku marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

LogPrint(BCLog::FEDERATION, "Generated new block: %s\n", block.GetHash().GetHex());
return true;
}

/** 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)
Expand Down Expand Up @@ -258,7 +435,35 @@ void ThreadBlockSigner(NodeContext& node)
break;
}

LogPrint(BCLog::FEDERATION, "A real block signer would generate a block here.\n");
{
LOCK(cs_block_signer);
using std::swap;
CBlock empty;
swap(g_block_proposal, empty);
g_block_sigs.clear();
}

if (!GenerateBlockProposal()) {
LogPrint(BCLog::FEDERATION, "%s: Unable to generate block proposal. Terminating round early.\n", __func__);
continue;
}

if (!AcceptBlockProposal()) {
LogPrint(BCLog::FEDERATION, "%s: Unable to validate our block proposal. Terminating round early.\n", __func__);
continue;
}

if (!SignBlock()) {
LogPrint(BCLog::FEDERATION, "%s: Unable to sign block. Terminating round early.\n", __func__);
continue;
}

if (!SubmitBlock()) {
LogPrint(BCLog::FEDERATION, "%s: Unable to submit block. Terminating round early.\n", __func__);
continue;
}

LogPrint(BCLog::FEDERATION, "Generated new block: %s\n", g_block_proposal.GetHash().GetHex());
}
}

Expand Down