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

NOMERGE: Support signet as parent chain #414

Open
wants to merge 6 commits into
base: elements-0.14.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ script:
- if [ "$RUN_TESTS" = "true" ]; then qa/pull-tester/rpc-tests.py --coverage; fi
- if [ "$RUN_FEDPEG_BITCOIND" = "true" ]; then BITCOIND_VERSION=0.16.3 && BITCOIND_ARCH=x86_64-linux-gnu; fi
- if [ "$RUN_FEDPEG_BITCOIND" = "true" ]; then wget https://bitcoincore.org/bin/bitcoin-core-$BITCOIND_VERSION/bitcoin-$BITCOIND_VERSION-$BITCOIND_ARCH.tar.gz && tar -zxf bitcoin-$BITCOIND_VERSION-$BITCOIND_ARCH.tar.gz && rm bitcoin-$BITCOIND_VERSION-$BITCOIND_ARCH.tar.gz; fi
- if [ "$RUN_FEDPEG_BITCOIND" = "true" ]; then qa/rpc-tests/feature_fedpeg.py --parent_bitcoin --parent_binpath $(pwd)/bitcoin-$BITCOIND_VERSION/bin/bitcoind; fi
- if [ "$RUN_FEDPEG_BITCOIND" = "true" ]; then qa/rpc-tests/feature_fedpeg.py --parent_type=bitcoin --parent_binpath $(pwd)/bitcoin-$BITCOIND_VERSION/bin/bitcoind; fi
after_script:
- echo $TRAVIS_COMMIT_RANGE
- echo $TRAVIS_COMMIT_LOG
95 changes: 83 additions & 12 deletions qa/rpc-tests/feature_fedpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,52 +33,113 @@ def __init__(self):
def add_options(self, parser):
parser.add_option("--parent_binpath", dest="parent_binpath", default="",
help="Use a different binary for launching nodes")
parser.add_option("--parent_bitcoin", dest="parent_bitcoin", default=False, action="store_true",
help="Parent nodes are Bitcoin")
parser.add_option("--parent_type", dest="parent_type", default="elements",
help="Type of parent nodes {elements, bitcoin, signet}")

def setup_network(self, split=False):
if self.options.parent_bitcoin and self.options.parent_binpath == "":
raise Exception("Can't run with --parent_bitcoin without specifying --parent_binpath")
self.nodes = []
self.extra_args = []

if self.options.parent_type not in ['elements', 'bitcoin', 'signet']:
raise Exception("Invalid option --parent_type=%s, valid values: {elements, bitcoin, signet}" % self.options.parent_type)

if self.options.parent_type == 'bitcoin' and self.options.parent_binpath == "":
raise Exception("Can't run with --parent_type=bitcoin without specifying --parent_binpath")

self.binary = self.options.parent_binpath if self.options.parent_binpath != "" else None

if self.options.parent_type == 'signet':
from binascii import hexlify
from test_framework import script, key
from test_framework.util import hex_str_to_bytes
import shutil
temp_args = [
"-port=" + str(p2p_port(0)),
"-rpcport=" + str(rpc_port(0)),
"-addresstype=legacy",
"-deprecatedrpc=validateaddress",
"-bech32_hrp=sb",
"-pchmessagestart=F0C7706A",
"-pubkeyprefix=125",
"-scriptprefix=87",
"-secretprefix=217",
"-extpubkeyprefix=043587CF",
"-extprvkeyprefix=04358394",
]
temp_node = start_node(0, self.options.tmpdir, temp_args, binary=self.binary, chain='temp', cookie_auth=True)
addr = temp_node.getnewaddress()
k = key.CECKey()
pub = temp_node.validateaddress(addr)["pubkey"]
k.set_pubkey(hex_str_to_bytes(pub))
pubkey = key.CPubKey(k.get_pubkey())
wif = temp_node.dumpprivkey(addr)
stop_node(temp_node, 0)
script = script.CScript([pubkey, script.OP_CHECKSIG])
blockscript = hexlify(script).decode('ascii')

print('blockscript', blockscript)
print('wif', wif)

self.parent_chain = self.options.parent_type
# Parent chain args
for n in range(2):
# We want to test the rpc cookie method so we force the use of a
# dummy conf file to avoid loading rpcuser/rpcpassword lines
use_cookie_auth = n==1
rpc_u, rpc_p = rpc_auth_pair(n)
if self.options.parent_bitcoin:
if self.options.parent_type == 'bitcoin':
self.parent_chain = 'regtest'
self.extra_args.append([
"-conf=dummy",
"-printtoconsole=0",
"-addresstype=legacy", # To make sure bitcoind gives back p2pkh no matter version
"-deprecatedrpc=validateaddress",
"-port="+str(p2p_port(n)),
"-rpcport="+str(rpc_port(n))
"-rpcport="+str(rpc_port(n)),
])
else:
self.parent_chain = 'parent'

elif self.options.parent_type == 'signet':
rpc_u, rpc_p = rpc_auth_pair(n)
self.extra_args.append([
"-printtoconsole=0",
"-signet_blockscript=%s" % blockscript,
"-signet_siglen=77",
"-port=" + str(p2p_port(n)),
"-rpcport=" + str(rpc_port(n)),
"-addresstype=legacy", # To make sure bitcoind gives back p2pkh no matter version
"-deprecatedrpc=validateaddress",
"-fallbackfee=0.00001",
"-bech32_hrp=sb",
"-pchmessagestart=F0C7706A",
"-pubkeyprefix=125",
"-scriptprefix=87",
"-secretprefix=217",
"-extpubkeyprefix=043587CF",
"-extprvkeyprefix=04358394",
])

elif self.options.parent_type == 'elements':
self.extra_args.append([
"-conf=dummy",
"-printtoconsole=0",
'-validatepegin=0',
'-anyonecanspendaremine',
'-initialfreecoins=2100000000000000',
"-port="+str(p2p_port(n)),
"-rpcport="+str(rpc_port(n))
"-rpcport="+str(rpc_port(n)),
])
# Only first parent uses name/password, the 2nd uses cookie auth
if not use_cookie_auth:
self.extra_args[n].extend(["-rpcuser="+rpc_u, "-rpcpassword="+rpc_p])

self.binary = self.options.parent_binpath if self.options.parent_binpath != "" else None
self.nodes.append(start_node(n, self.options.tmpdir, self.extra_args[n], binary=self.binary, chain=self.parent_chain, cookie_auth=use_cookie_auth))
if self.options.parent_type == 'signet':
self.nodes[n].importprivkey(wif)

connect_nodes_bi(self.nodes, 0, 1)
self.parentgenesisblockhash = self.nodes[0].getblockhash(0)
print('parentgenesisblockhash', self.parentgenesisblockhash)
if not self.options.parent_bitcoin:
if self.options.parent_type == 'elements':
parent_pegged_asset = self.nodes[0].getsidechaininfo()['pegged_asset']

# Sidechain args
Expand All @@ -99,14 +160,24 @@ def setup_network(self, split=False):
'-mainchainrpcport=%s' % rpc_port(n),
'-recheckpeginblockinterval=15', # Long enough to allow failure and repair before timeout
]
if not self.options.parent_bitcoin:

if self.options.parent_type == 'elements':
args.extend([
'-parentpubkeyprefix=235',
'-parentscriptprefix=75',
'-con_parent_chain_signblockscript=%s' % parent_chain_signblockscript,
'-con_parent_pegged_asset=%s' % parent_pegged_asset,
])

elif self.options.parent_type == 'signet':
args.extend([
'-con_parent_is_signet=1',
'-con_parent_signet_siglen=77',
'-parentpubkeyprefix=125',
'-parentscriptprefix=87',
'-con_parent_chain_signblockscript=%s' % blockscript,
])

if used_cookie_auth:
# Need to specify where to find parent cookie file
datadir = os.path.join(self.options.tmpdir, "node"+str(n))
Expand Down
5 changes: 4 additions & 1 deletion src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "chainparams.h"
#include "consensus/merkle.h"
#include "issuance.h"

#include "primitives/bitcoin/block.h"
#include "tinyformat.h"
#include "util.h"
#include "utilstrencodings.h"
Expand Down Expand Up @@ -131,6 +131,9 @@ class CCustomParams : public CChainParams {
consensus.pegin_min_depth = GetArg("-peginconfirmationdepth", DEFAULT_PEGIN_CONFIRMATION_DEPTH);
consensus.mandatory_coinbase_destination = StrHexToScriptWithDefault(GetArg("-con_mandatorycoinbase", ""), CScript()); // Blank script allows any coinbase destination
consensus.parent_chain_signblockscript = StrHexToScriptWithDefault(GetArg("-con_parent_chain_signblockscript", ""), CScript());
consensus.parent_is_signet = GetBoolArg("-con_parent_is_signet", false);
g_solution_blocks = consensus.parent_is_signet;
g_solution_block_len = GetArg("-con_parent_signet_siglen", 77);
consensus.parent_pegged_asset.SetHex(GetArg("-con_parent_pegged_asset", "0x00"));

// bitcoin regtest is the parent chain by default
Expand Down
4 changes: 3 additions & 1 deletion src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ struct Params {
CScript signblockscript;
bool has_parent_chain;
CScript parent_chain_signblockscript;
bool parent_is_signet;
CAsset parent_pegged_asset;
bool ParentChainHasPow() const { return parent_chain_signblockscript == CScript();}
bool ParentChainHasPow() const { return parent_chain_signblockscript == CScript(); }
bool ParentChainIsBitcoinLike() const { return parent_is_signet || ParentChainHasPow(); }
};
} // namespace Consensus

Expand Down
2 changes: 2 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,8 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-parentpubkeyprefix", strprintf(_("The byte prefix, in decimal, of the parent chain's base58 pubkey address. (default: %d)"), 111));
strUsage += HelpMessageOpt("-parentscriptprefix", strprintf(_("The byte prefix, in decimal, of the parent chain's base58 script address. (default: %d)"), 196));
strUsage += HelpMessageOpt("-con_parent_chain_signblockscript", _("Whether parent chain uses pow or signed blocks. If the parent chain uses signed blocks, the challenge (scriptPubKey) script. If not, an empty string. (default: empty script [ie parent uses pow])"));
strUsage += HelpMessageOpt("-con_parent_is_signet", _("If parent chain uses signed blocks, whether it is an elements based chain or one based on signet (default: false)"));
strUsage += HelpMessageOpt("-con_parent_signet_siglen", _("If parent chain uses signed blocks based on signet, The length of the signature must be exactly this long (padded to this length, if shorter). All block headers in this network are of length 80 + this value. (default: 77)"));
strUsage += HelpMessageOpt("-con_parent_pegged_asset=<hex>", _("Asset ID (hex) for pegged asset for when parent chain has CA. (default: 0x00)"));
strUsage += HelpMessageOpt("-recheckpeginblockinterval", strprintf(_("The interval in seconds at which a peg-in witness failing block is re-evaluated in case of intermittant peg-in witness failure. 0 means never. (default: %u)"), 120));
}
Expand Down
26 changes: 24 additions & 2 deletions src/pow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,38 @@ void ResetChallenge(CBlockHeader& block, const CBlockIndex& indexLast, const Con
block.proof.challenge = indexLast.proof.challenge;
}

bool CheckBitcoinProof(uint256 hash, unsigned int nBits)
static const unsigned int BLOCK_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY_FLAGS |
SCRIPT_VERIFY_DERSIG |
SCRIPT_VERIFY_STRICTENC |
SCRIPT_VERIFY_MINIMALDATA |
SCRIPT_VERIFY_NULLDUMMY |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS |
SCRIPT_VERIFY_CLEANSTACK |
SCRIPT_VERIFY_MINIMALIF |
SCRIPT_VERIFY_NULLFAIL |
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY |
SCRIPT_VERIFY_CHECKSEQUENCEVERIFY |
SCRIPT_VERIFY_LOW_S |
SCRIPT_VERIFY_WITNESS |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM |
SCRIPT_VERIFY_WITNESS_PUBKEYTYPE;

bool CheckBitcoinProof(const uint256& hash, unsigned int nBits, const Consensus::Params& params)
{
if (g_solution_blocks) {
const auto& payload = g_blockheader_payload_map.at(hash);
CScript solution = CScript(payload.begin(), payload.end());
return HashVerifyScript(solution, params.parent_chain_signblockscript, BLOCK_VERIFY_FLAGS, hash);
}

bool fNegative;
bool fOverflow;
arith_uint256 bnTarget;

bnTarget.SetCompact(nBits, &fNegative, &fOverflow);

// Check range
if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(Params().GetConsensus().parentChainPowLimit))
if (fNegative || bnTarget == 0 || fOverflow || bnTarget > UintToArith256(params.parentChainPowLimit))
return false;

// Check proof of work matches claimed amount
Expand Down
2 changes: 1 addition & 1 deletion src/pow.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CWallet;
class uint256;

/** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
bool CheckBitcoinProof(uint256 hash, unsigned int nBits);
bool CheckBitcoinProof(const uint256& hash, unsigned int nBits, const Consensus::Params& params);
bool CheckProofSignedParent(const CBlockHeader& block, const Consensus::Params& params);
bool CheckProof(const CBlockHeader& block, const Consensus::Params&);
/** Scans nonces looking for a hash with at least some zero bits */
Expand Down
4 changes: 4 additions & 0 deletions src/primitives/bitcoin/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
#include "utilstrencodings.h"
#include "crypto/common.h"

bool g_solution_blocks = false;
size_t g_solution_block_len = 0;
std::map<uint256,std::vector<uint8_t>> g_blockheader_payload_map;

namespace Sidechain {
namespace Bitcoin {

Expand Down
25 changes: 25 additions & 0 deletions src/primitives/bitcoin/block.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@
#include "serialize.h"
#include "uint256.h"

/**
* If true, block headers contain a payload equal to a Bitcoin Script solution
* to a signet challenge as defined in the chain params.
*/
extern bool g_solution_blocks;
/**
* If non-zero, defines an enforced size requirement for block header payloads.
* It requires that all blocks are of size 80 + (this value) bytes.
*/
extern size_t g_solution_block_len;
/**
* Contains a mapping of hash to signature data for each block header
* in signet networks.
*/
extern std::map<uint256,std::vector<uint8_t>> g_blockheader_payload_map;
Copy link
Member

Choose a reason for hiding this comment

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

Is this supposed to be like a global cache? It doesn't seem to get emptied ever.

Copy link
Collaborator

Choose a reason for hiding this comment

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

An in-memory cache that, most importantly, doesn't inflate memory usage when signet feature is off.

This does mean an ever-growing set when on, yes. Wonder what cane be done with this.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@kallewoof thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

It's ever growing, yep. It could be cached since header sigs are stored along with headers. I haven't thought too much about it, as even a 1 million block chain (that's 2x the lifetime of bitcoin right now) would only have ~100 MB of RAM use.

Copy link
Member

Choose a reason for hiding this comment

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

Also when it's a 11-of-15 multisig? Or even larger if this script doesn't have the same script limits as normal tx scripts.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right you would basically store siglen + alpha per block. For a 15 multisig over ECDSA it'd get big quicker, I suspect, but I assume we will eventually switch to Schnorr multisigs long term.


namespace Sidechain {
namespace Bitcoin {

Expand Down Expand Up @@ -48,6 +64,15 @@ class CBlockHeader
READWRITE(nTime);
READWRITE(nBits);
READWRITE(nNonce);
if (g_solution_blocks && !(s.GetType() & SER_GETHASH)) {
READWRITE(g_blockheader_payload_map[GetHash()]);
size_t len = GetSizeOfCompactSize(g_blockheader_payload_map[GetHash()].size()) + g_blockheader_payload_map[GetHash()].size();
while (len < g_solution_block_len) {
uint8_t padding = 0;
READWRITE(padding);
len++;
}
}
}

void SetNull()
Expand Down
6 changes: 6 additions & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,8 @@ UniValue getsidechaininfo(const JSONRPCRequest& request)
" \"min_peg_diff\" : \"xxxx\", (string) The minimum difficulty parent chain header target. Peg-in headers that have less work will be rejected as an anti-Dos measure.\n"
" \"parent_blockhash\" : \"xxxx\", (string) The parent genesis blockhash as source of pegged-in funds.\n"
" \"parent_chain_has_pow\": \"xxxx\", (boolean) Whether parent chain has pow or signed blocks.\n"
" \"parent_chain_is_signet\": \"xxxx\", (boolean) If parent chain uses signed blocks, whether it is an elements based chain or one based on signet.\n"
" \"parent_chain_signet_siglen\": \"xxxx\", (numeric) If parent chain uses signed blocks based on signet, The length of the signature must be exactly this long (padded to this length, if shorter). All block headers in this network are of length 80 + this value.\n"
" \"parent_chain_signblockscript_asm\": \"xxxx\", (string) If the parent chain has signed blocks, its signblockscript in ASM.\n"
" \"parent_chain_signblockscript_hex\": \"xxxx\", (string) If the parent chain has signed blocks, its signblockscript in hex.\n"
" \"parent_pegged_asset\": \"xxxx\", (boolean) If the parent chain has Confidential Assets, the asset id of the pegged asset in that chain.\n"
Expand All @@ -1133,6 +1135,10 @@ UniValue getsidechaininfo(const JSONRPCRequest& request)
obj.push_back(Pair("parent_blockhash", parent_blockhash.GetHex()));
obj.push_back(Pair("parent_chain_has_pow", consensus.ParentChainHasPow()));
if (!consensus.ParentChainHasPow()) {
obj.push_back(Pair("parent_chain_is_signet", consensus.parent_is_signet));
if (consensus.parent_is_signet) {
obj.push_back(Pair("parent_chain_signet_siglen", (uint64_t)g_solution_block_len));
}
obj.push_back(Pair("parent_chain_signblockscript_asm", ScriptToAsmStr(consensus.parent_chain_signblockscript)));
obj.push_back(Pair("parent_chain_signblockscript_hex", HexStr(consensus.parent_chain_signblockscript)));
obj.push_back(Pair("parent_pegged_asset", HexStr(consensus.parent_pegged_asset)));
Expand Down
5 changes: 5 additions & 0 deletions src/script/generic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ bool GenericVerifyScript(const CScript& scriptSig, const CScript& scriptPubKey,
return VerifyScript(scriptSig, scriptPubKey, NULL, flags, SimpleSignatureChecker(SerializeHash(data)));
}

bool HashVerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, unsigned int flags, const uint256& hash)
{
return VerifyScript(scriptSig, scriptPubKey, NULL, flags, SimpleSignatureChecker(hash));
}

template<typename T>
bool GenericSignScript(const CKeyStore& keystore, const T& data, const CScript& fromPubKey, SignatureData& scriptSig)
{
Expand Down
4 changes: 2 additions & 2 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2546,13 +2546,13 @@ bool IsValidPeginWitness(const CScriptWitness& pegin_witness, const COutPoint& p
uint256 tx_hash;
int num_txs;
// Get txout proof
if (Params().GetConsensus().ParentChainHasPow()) {
if (Params().GetConsensus().ParentChainIsBitcoinLike()) {

Sidechain::Bitcoin::CMerkleBlock merkle_block_pow;
if (!GetBlockAndTxFromMerkleBlock(block_hash, tx_hash, merkle_block_pow, stack[5])) {
return false;
}
if (!CheckBitcoinProof(block_hash, merkle_block_pow.header.nBits)) {
if (!CheckBitcoinProof(block_hash, merkle_block_pow.header.nBits, Params().GetConsensus())) {
return false;
}

Expand Down
4 changes: 2 additions & 2 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3716,12 +3716,12 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
UniValue createrawpegin(const JSONRPCRequest& request)
{
UniValue ret(UniValue::VOBJ);
if (Params().GetConsensus().ParentChainHasPow()) {
if (Params().GetConsensus().ParentChainIsBitcoinLike()) {
Sidechain::Bitcoin::CTransactionRef txBTCRef;
Sidechain::Bitcoin::CTransaction tx_aux;
Sidechain::Bitcoin::CMerkleBlock merkleBlock;
ret = createrawpegin(request, txBTCRef, tx_aux, merkleBlock);
if (!CheckBitcoinProof(merkleBlock.header.GetHash(), merkleBlock.header.nBits)) {
if (!CheckBitcoinProof(merkleBlock.header.GetHash(), merkleBlock.header.nBits, Params().GetConsensus())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
}
} else {
Expand Down