diff --git a/contrib/signet/README.md b/contrib/signet/README.md new file mode 100644 index 000000000000..e39327b6e5bc --- /dev/null +++ b/contrib/signet/README.md @@ -0,0 +1,60 @@ +Contents +======== +This directory contains tools related to Signet, both for running a Signet yourself and for using one. + +The `args.sh` script is a helper script used by the other scripts and should not be invoked directly. + +getcoins.sh +=========== + +A script to call a faucet to get Signet coins. + +Syntax: `getcoins.sh [--help] [--cmd=] [--faucet=] [--addr=] [--password=] [--] []` + +* `--cmd` lets you customize the bitcoin-cli path. By default it will look for it in the PATH, then in `../../src/` +* `--faucet` lets you specify which faucet to use; the faucet is assumed to be compatible with https://github.com/kallewoof/bitcoin-faucet +* `--addr` lets you specify a Signet address; by default, the address must be a bech32 address. This and `--cmd` above complement each other (i.e. you do not need `bitcoin-cli` if you use `--addr`) +* `--password` lets you specify a faucet password; this is handy if you are in a classroom and set up your own faucet for your students; (above faucet does not limit by IP when password is enabled) + +If using the default network, invoking the script with no arguments should be sufficient under normal +circumstances, but if multiple people are behind the same IP address, the faucet will by default only +accept one claim per day. See `--password` above. + +issuer.sh +========= + +A script to regularly issue Signet blocks. + +Syntax: `issuer.sh [--help] [--cmd=] [--] []` + +* `` is a time in seconds to wait between each block generation +* `--cmd` lets you customize the bitcoin-cli path. By default it will look for it in the PATH, then in `../../src/` + +Signet, just like other bitcoin networks, uses proof of work alongside the block signature; this +includes the difficulty adjustment every 2016 blocks. +The `` exists to allow you to maintain a relatively low difficulty over an extended period +of time. E.g. an idle time of 540 means your node will end up spending roughly 1 minute grinding +hashes for each block, and wait 9 minutes after every time. + +mkblock.sh +========== + +A script to generate one Signet block. + +Syntax: `mkblock.sh []` + +This script is called by the other block issuing scripts, but can be invoked independently to generate +1 block immediately. + +secondary.sh +============ + +A script to act as backup generator in case the primary issuer goes offline. + +Syntax: `secondary.sh [--cmd=] []` + +* `` is the time in seconds that must have passed since the last block was seen for the secondary issuer to kick into motion +* `` is the time in seconds to wait after generating a block, and should preferably be the same as the idle time of the main issuer + +Running a Signet network, it is recommended to have at least one secondary running in a different +place, so it doesn't go down together with the main issuer. diff --git a/contrib/signet/addtxtoblock.py b/contrib/signet/addtxtoblock.py new file mode 100755 index 000000000000..ba8ededc7163 --- /dev/null +++ b/contrib/signet/addtxtoblock.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import sys +import os +import argparse + +# dirty hack but makes CBlock etc available +sys.path.append('/'.join(os.getcwd().split('/')[:-2]) + '/test/functional') + +from test_framework.messages import ( + CBlock, + CTransaction, + FromHex, + ToHex, +) + +from test_framework.blocktools import add_witness_commitment + +def main(): + + # Parse arguments and pass through unrecognised args + parser = argparse.ArgumentParser(add_help=False, + usage='%(prog)s [addtxtoblock options] [bitcoin block file] [tx file] [fee]', + description=__doc__, + epilog='''Help text and arguments:''', + formatter_class=argparse.RawTextHelpFormatter) + _, unknown_args = parser.parse_known_args() + + if len(unknown_args) != 3: + print("Need three arguments (block file, tx file, and fee)") + sys.exit(1) + + [blockfile, txfile, feestr] = unknown_args + + with open(blockfile, "r", encoding="utf8") as f: + blockhex = f.read().strip() + with open(txfile, "r", encoding="utf8") as f: + txhex = f.read().strip() + + fee = int(feestr) + + block = CBlock() + FromHex(block, blockhex) + + tx = CTransaction() + FromHex(tx, txhex) + + block.vtx[0].vout[0].nValue += fee + block.vtx.append(tx) + add_witness_commitment(block) + print(ToHex(block)) + +if __name__ == '__main__': + main() diff --git a/contrib/signet/args.sh b/contrib/signet/args.sh new file mode 100755 index 000000000000..cce81a78a72d --- /dev/null +++ b/contrib/signet/args.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C + +args="-signet" +eoc= + +command -v bitcoin-cli > /dev/null \ +&& bcli="bitcoin-cli" \ +|| bcli="$(dirname $0)/../../src/bitcoin-cli" + +if [ "$VARCHECKS" = "" ]; then + VARCHECKS='if [ "$varname" = "cmd" ]; then bcli=$value;' +fi + +# compatibility; previously the bitcoin-cli path was given as first argument +if [[ "$1" != "" && "${1:$((${#1}-11))}" = "bitcoin-cli" ]]; then + echo "using $1 as bcli" + bcli=$1 + shift +fi + +for i in "$@"; do + if [ $eoc ]; then + args="$args $i" + elif [ "$i" = "--" ]; then + # end of commands; rest into args for bitcoin-cli + eoc=1 + continue + elif [ "${i:0:2}" = "--" ]; then + # command + j=${i:2} + if [ "$j" = "help" ]; then + >&2 echo -e $HELPSTRING # "syntax: $0 [--help] [--cmd=] [--faucet==https://signet.bc-2.jp/claim] []" + exit 1 + fi + export varname=${j%=*} + export value=${j#*=} + eval $VARCHECKS ' + else + >&2 echo "unknown parameter $varname (from \"$i\"); for help, type: $0 --help" + exit 1 + fi' + # if [ "$varname" = "cmd" ]; then + # bcli=$value + # elif [ "$varname" = "faucet" ]; then + # faucet=$value + else + # arg + args="$args $i" + fi +done + +if ! [ -e "$bcli" ]; then + command -v "$bcli" >/dev/null 2>&1 || { echo >&2 "error: unable to find bitcoin binary: $bcli"; exit 1; } +fi diff --git a/contrib/signet/forker.sh b/contrib/signet/forker.sh new file mode 100755 index 000000000000..72c5c5584fc0 --- /dev/null +++ b/contrib/signet/forker.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C + +# +# Take the hex formatted line separated transactions in reorg-list.txt where every even transaction is put into the even set, and every odd transaction is put into the odd set. +# That is, transactions on line 1, 3, 5, 7, ... go into the odd set, and transactions on line 2, 4, 6, 8, ... go into the even set. +# +# - Generate two blocks A and B, where A contains all or some of the odd set, and B contains the corresponding even set. +# - Sign, grind, and broadcast block A +# - Wait a random amount of time (1-10 mins) +# - Invalidate block A +# - Sign, grind, and broadcast block B +# + +bcli=$1 +shift + +function log() +{ + echo "- $(date +%H:%M:%S): $*" +} + +if [ ! -e "reorg-list.txt" ]; then + echo "reorg-list.txt not found" + exit 1 +fi + +# get address for coinbase output +addr=$($bcli "$@" getnewaddress) + +# create blocks A and B +$bcli "$@" getnewblockhex $addr > $PWD/block-a +cp block-a block-b + +odd=1 +while read -r line; do + if [ "$line" = "" ]; then continue; fi + echo $line > tx + if [ $odd -eq 1 ]; then blk="block-a"; else blk="block-b"; fi + ./addtxtoblock.py $blk tx 100 > t # note: we are throwing away all fees above 100 satoshis for now; should figure out a way to determine fees + mv t $blk + (( odd=1-odd )) +done < reorg-list.txt + +rm reorg-list.txt + +log "mining block A (to-orphan block)" +while true; do + $bcli "$@" signblock $PWD/block-a > signed-a + blockhash_a=$($bcli "$@" grindblock $PWD/signed-a 1000000000) + if [ "$blockhash_a" != "false" ]; then break; fi +done +log "mined block with hash $blockhash_a" +(( waittime=RANDOM%570 )) +(( waittime=30+waittime )) +log "waiting for $waittime s" +sleep $waittime +log "invalidating $blockhash_a" +$bcli "$@" invalidateblock $blockhash_a + +log "mining block B (replace block)" +while true; do + $bcli "$@" signblock $PWD/block-b > signed-b + blockhash_b=$($bcli "$@" grindblock $PWD/signed-b 1000000000) + if [ "$blockhash_b" != "false" ]; then break; fi +done + +echo "mined $blockhash_b" +echo "cleaning up" +rm block-b signed-b block-a signed-a diff --git a/contrib/signet/getcoins.sh b/contrib/signet/getcoins.sh new file mode 100755 index 000000000000..8a2fedf8ae49 --- /dev/null +++ b/contrib/signet/getcoins.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C + +# +# Get coins from Signet Faucet +# + +export VARCHECKS=' + if [ "$varname" = "cmd" ]; then + bcli=$value; + elif [ "$varname" = "faucet" ]; then + faucet=$value; + elif [ "$varname" = "addr" ]; then + addr=$value; + elif [ "$varname" = "password" ]; then + password=$value; + ' +export HELPSTRING="syntax: $0 [--help] [--cmd=] [--faucet=] [--addr=] [--password=] [--] []" + +bcli= +args= +password= +addr= +faucet="https://signet.bc-2.jp/claim" + +# shellcheck source=contrib/signet/args.sh +source $(dirname $0)/args.sh "$@" + +if [ "$addr" = "" ]; then + # get address for receiving coins + addr=$($bcli $args getnewaddress faucet bech32) || { echo >&2 "for help, type: $0 --help"; exit 1; } +fi + +# shellcheck disable=SC2015 +command -v "curl" > /dev/null \ +&& curl -X POST -d "address=$addr&password=$password" $faucet \ +|| wget -qO - --post-data "address=$addr&password=$password" $faucet + +echo diff --git a/contrib/signet/issuer.sh b/contrib/signet/issuer.sh new file mode 100755 index 000000000000..a83e82641e14 --- /dev/null +++ b/contrib/signet/issuer.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C + +# +# Issue blocks using a local node at a given interval. +# + +export HELPSTRING="syntax: $0 [--help] [--cmd=] [--] []" + +if [ $# -lt 1 ]; then + echo $HELPSTRING + exit 1 +fi + +function log() +{ + echo "- $(date +%H:%M:%S): $*" +} + +idletime=$1 +shift + +bcli= +args= + +# shellcheck source=contrib/signet/args.sh +source $(dirname $0)/args.sh "$@" + +MKBLOCK=$(dirname $0)/mkblock.sh + +if [ ! -e "$MKBLOCK" ]; then + >&2 echo "error: cannot locate mkblock.sh (expected to find in $MKBLOCK" + exit 1 +fi + +echo "- checking node status" +conns=$($bcli $args getconnectioncount) || { echo >&2 "node error"; exit 1; } + +if [ $conns -lt 1 ]; then + echo "warning: node is not connected to any other node" +fi + +log "node OK with $conns connection(s)" +log "mining at maximum capacity with $idletime second delay between each block" +log "hit ^C to stop" + +while true; do + if [ -e "reorg-list.txt" ]; then + ./forker.sh $bcli $args + else + log "generating next block" + blockhash=$("$MKBLOCK" "$bcli" $args) || { echo "node error; aborting" ; exit 1; } + log "mined block $($bcli $args getblockcount) $blockhash to $($bcli $args getconnectioncount) peer(s); idling for $idletime seconds" + fi + sleep $idletime +done diff --git a/contrib/signet/mkblock.sh b/contrib/signet/mkblock.sh new file mode 100755 index 000000000000..e63bdec575b2 --- /dev/null +++ b/contrib/signet/mkblock.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C + +# +# Generate a block +# + +if [ $# -lt 1 ]; then + >&2 echo "syntax: $0 []" ; exit 1 +fi + +bcli=$1 +shift + +if ! [ -e "$bcli" ]; then + command -v "$bcli" >/dev/null 2>&1 || { echo >&2 "error: unable to find bitcoin binary: $bcli"; exit 1; } +fi + +# get address for coinbase output +addr=$($bcli "$@" getnewaddress) +# start looping; we re-create the block every time we fail to grind as that resets the nonce and gives us an updated +# version of the block +while true; do + # create an unsigned, un-PoW'd block + $bcli "$@" getnewblockhex $addr > $PWD/unsigned + # sign it + $bcli "$@" signblock $PWD/unsigned > $PWD/signed + # grind proof of work; this ends up broadcasting the block, if successful (akin to "generatetoaddress") + blockhash=$($bcli "$@" grindblock $PWD/signed 10000000) + if [ "$blockhash" != "false" ]; then break; fi +done + +echo $blockhash diff --git a/contrib/signet/secondary.sh b/contrib/signet/secondary.sh new file mode 100755 index 000000000000..b3d4e33f7bed --- /dev/null +++ b/contrib/signet/secondary.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +export LC_ALL=C + +# +# Backup Issuer which will begin to issue blocks if it sees no blocks within +# a specified period of time. It will continue to make blocks until a block +# is generated from an external source (i.e. the primary issuer is back online) +# + +export HELPSTRING="syntax: $0 [--cmd=] []" + +if [ $# -lt 3 ]; then + echo $HELPSTRING + exit 1 +fi + +function log() +{ + echo "- $(date +%H:%M:%S): $*" +} + +triggertime=$1 +shift + +idletime=$1 +shift + +bcli= +args= + +# shellcheck source=contrib/signet/args.sh +source $(dirname $0)/args.sh "$@" + +echo "- checking node status" +conns=$($bcli $args getconnectioncount) || { echo >&2 "node error"; exit 1; } + +if [ $conns -lt 1 ]; then + echo "warning: node is not connected to any other node" +fi + +MKBLOCK=$(dirname $0)/mkblock.sh + +if [ ! -e "$MKBLOCK" ]; then + >&2 echo "error: cannot locate mkblock.sh (expected to find in $MKBLOCK" + exit 1 +fi + +log "node OK with $conns connection(s)" +log "hit ^C to stop" + +# outer loop alternates between watching and mining +while true; do + # inner watchdog loop + blocks=$($bcli $args getblockcount) + log "last block #$blocks; waiting up to $triggertime seconds for a new block" + remtime=$triggertime + while true; do + waittime=1800 + if [ $waittime -gt $remtime ]; then waittime=$remtime; fi + conns=$($bcli $args getconnectioncount) + if [ $conns -eq 1 ]; then s=""; else s="s"; fi + log "waiting $waittime/$remtime seconds with $conns peer$s" + sleep $waittime + new_blocks=$($bcli $args getblockcount) + if [ $new_blocks -gt $blocks ]; then + log "detected block count increase $blocks -> $new_blocks; resetting timer" + remtime=$triggertime + blocks=$new_blocks + else + (( remtime=remtime-waittime )) + if [ $remtime -lt 1 ]; then break; fi + fi + done + log "*** no blocks in $triggertime seconds; initiating issuance ***" + # inner issuer loop + while true; do + log "generating next block" + blockhash=$("$MKBLOCK" "$bcli" "$2") || { echo "node error; aborting" ; exit 1; } + blocks=$($bcli $args getblockcount) + log "mined block $new_blocks $blockhash to $($bcli $args getconnectioncount) peer(s); idling for $idletime seconds" + sleep $idletime + new_blocks=$($bcli $args getblockcount) + if [ $blocks -lt $new_blocks ]; then + log "primary issuer appears to be back online ($blocks -> $new_blocks blocks during downtime); going back to watching" + break + fi + done +done diff --git a/src/Makefile.am b/src/Makefile.am index 27c87688b42e..d4516d06efea 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -195,6 +195,7 @@ BITCOIN_CORE_H = \ script/signingprovider.h \ script/standard.h \ shutdown.h \ + signet.h \ streams.h \ support/allocators/secure.h \ support/allocators/zeroafterfree.h \ @@ -484,6 +485,7 @@ libbitcoin_common_a_SOURCES = \ script/sign.cpp \ script/signingprovider.cpp \ script/standard.cpp \ + signet.cpp \ versionbitsinfo.cpp \ warnings.cpp \ $(BITCOIN_CORE_H) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 0a1cb858ef56..634f05684868 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -7,6 +7,8 @@ #include #include +#include // for signet block challenge hash +#include #include #include #include @@ -17,13 +19,13 @@ #include #include -static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesisOutputScript, uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward) +static inline CBlock CreateGenesisBlock(const CScript& coinbase_sig, const CScript& genesisOutputScript, uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward) { CMutableTransaction txNew; txNew.nVersion = 1; txNew.vin.resize(1); txNew.vout.resize(1); - txNew.vin[0].scriptSig = CScript() << 486604799 << CScriptNum(4) << std::vector((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); + txNew.vin[0].scriptSig = coinbase_sig; txNew.vout[0].nValue = genesisReward; txNew.vout[0].scriptPubKey = genesisOutputScript; @@ -38,6 +40,12 @@ static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesi return genesis; } +static inline CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesisOutputScript, uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward) +{ + CScript coinbase_sig = CScript() << 486604799 << CScriptNum(4) << std::vector((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); + return CreateGenesisBlock(coinbase_sig, genesisOutputScript, nTime, nNonce, nBits, nVersion, genesisReward); +} + /** * Build the genesis block. Note that the output of its generation * transaction cannot be spent since it did not originally exist in the @@ -49,13 +57,23 @@ static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesi * CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) * vMerkleTree: 4a5e1e */ -static CBlock CreateGenesisBlock(uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward) +static inline CBlock CreateGenesisBlock(uint32_t nTime, uint32_t nNonce, uint32_t nBits, int32_t nVersion, const CAmount& genesisReward) { const char* pszTimestamp = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"; const CScript genesisOutputScript = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; return CreateGenesisBlock(pszTimestamp, genesisOutputScript, nTime, nNonce, nBits, nVersion, genesisReward); } +CBlock CreateSignetGenesisBlock(const CScript& block_script, uint32_t block_nonce) +{ + CHashWriter h(SER_DISK, 0); + h << block_script; + uint256 hash = h.GetHash(); + CScript coinbase_sig = CScript() << std::vector(hash.begin(), hash.end()); + CScript genesis_out = CScript() << OP_RETURN; + return CreateGenesisBlock(coinbase_sig, genesis_out, 1534313275, block_nonce, 0x1e2adc28, 1, 50 * COIN); +} + /** * Main network */ @@ -63,6 +81,7 @@ class CMainParams : public CChainParams { public: CMainParams() { strNetworkID = CBaseChainParams::MAIN; + consensus.signet_blocks = false; consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22"); consensus.BIP34Height = 227931; @@ -170,6 +189,7 @@ class CTestNetParams : public CChainParams { public: CTestNetParams() { strNetworkID = CBaseChainParams::TESTNET; + consensus.signet_blocks = false; consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105"); consensus.BIP34Height = 21111; @@ -248,6 +268,93 @@ class CTestNetParams : public CChainParams { } }; +/** + * SigNet + */ +class SigNetParams : public CChainParams { +public: + explicit SigNetParams(const ArgsManager& args) { + std::vector bin; + vSeeds.clear(); + uint32_t genesis_nonce = 0; + + if (!args.IsArgSet("-signet_blockscript")) { + LogPrintf("Using default signet network\n"); + bin = ParseHex("512103ad5e0edad18cb1f0fc0d28a3d4f1f3e445640337489abb10404f2d1e086be43051ae"); + genesis_nonce = 621297; + vSeeds.push_back("178.128.221.177"); + vSeeds.push_back("2a01:7c8:d005:390::5"); + vSeeds.push_back("ntv3mtqw5wt63red.onion:38333"); + } else { + if (args.GetArgs("-signet_blockscript").size() != 1) { + throw std::runtime_error(strprintf("%s: -signet_blockscript cannot be multiple values.", __func__)); + } + bin = ParseHex(args.GetArgs("-signet_blockscript")[0]); + genesis_nonce = args.GetArg("-signet_genesisnonce", 0); + if (args.IsArgSet("-signet_seednode")) { + vSeeds = gArgs.GetArgs("-signet_seednode"); + } + + LogPrintf("SigNet with block script %s\n", gArgs.GetArgs("-signet_blockscript")[0]); + } + + strNetworkID = CBaseChainParams::SIGNET; + g_signet_blockscript = CScript(bin.begin(), bin.end()); + consensus.signet_blocks = true; + consensus.nSubsidyHalvingInterval = 210000; + consensus.BIP34Height = 1; + consensus.BIP65Height = 1; + consensus.BIP66Height = 1; + consensus.CSVHeight = 0; + consensus.SegwitHeight = 0; + consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks + consensus.nPowTargetSpacing = 10 * 60; + consensus.fPowAllowMinDifficultyBlocks = false; + consensus.fPowNoRetargeting = false; + consensus.nRuleChangeActivationThreshold = 1916; + consensus.nMinerConfirmationWindow = 2016; + consensus.powLimit = uint256S("00002adc28cf53b63c82faa55d83e40ac63b5f100aa5d8df62a429192f9e8ce5"); + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1539478800; + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; + + pchMessageStart[0] = 0xf0; + pchMessageStart[1] = 0xc7; + pchMessageStart[2] = 0x70; + pchMessageStart[3] = 0x6a; + nDefaultPort = 38333; + nPruneAfterHeight = 1000; + + genesis = CreateSignetGenesisBlock(g_signet_blockscript, genesis_nonce); + consensus.hashGenesisBlock = genesis.GetHash(); + + // Now that genesis block has been generated, we check if there is an enforcescript, and switch + // to that one, as we will not be using the real block script anymore + if (args.IsArgSet("-signet_enforcescript")) { + if (args.GetArgs("-signet_enforcescript").size() != 1) { + throw std::runtime_error(strprintf("%s: -signet_enforcescript cannot be multiple values.", __func__)); + } + bin = ParseHex(args.GetArgs("-signet_enforcescript")[0]); + g_signet_blockscript = CScript(bin.begin(), bin.end()); + LogPrintf("SigNet enforce script %s\n", gArgs.GetArgs("-signet_enforcescript")[0]); + } + + vFixedSeeds.clear(); + + base58Prefixes[PUBKEY_ADDRESS] = std::vector{125}; + base58Prefixes[SCRIPT_ADDRESS] = std::vector{87}; + base58Prefixes[SECRET_KEY] = std::vector{217}; + base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; + base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; + + bech32_hrp = "sb" + args.GetArg("-signet_hrp", ""); + + fDefaultConsistencyChecks = false; + fRequireStandard = true; + m_is_test_chain = true; + } +}; + /** * Regression test */ @@ -255,6 +362,7 @@ class CRegTestParams : public CChainParams { public: explicit CRegTestParams(const ArgsManager& args) { strNetworkID = CBaseChainParams::REGTEST; + consensus.signet_blocks = false; consensus.nSubsidyHalvingInterval = 150; consensus.BIP16Exception = uint256(); consensus.BIP34Height = 500; // BIP34 activated on regtest (Used in functional tests) @@ -394,6 +502,9 @@ std::unique_ptr CreateChainParams(const std::string& chain) return std::unique_ptr(new CTestNetParams()); else if (chain == CBaseChainParams::REGTEST) return std::unique_ptr(new CRegTestParams(gArgs)); + else if (chain == CBaseChainParams::SIGNET) { + return std::unique_ptr(new SigNetParams(gArgs)); + } throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); } @@ -402,3 +513,4 @@ void SelectParams(const std::string& network) SelectBaseParams(network); globalChainParams = CreateChainParams(network); } + diff --git a/src/chainparams.h b/src/chainparams.h index 6be066806bcf..90c8a4e8f455 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -125,4 +125,9 @@ const CChainParams &Params(); */ void SelectParams(const std::string& chain); +/** + * Create a Signet genesis block for the given block script/nonce. + */ +CBlock CreateSignetGenesisBlock(const CScript& block_script, uint32_t block_nonce=0); + #endif // BITCOIN_CHAINPARAMS_H diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 4bb66c8d8b51..5231ae5f97fe 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -13,6 +13,7 @@ const std::string CBaseChainParams::MAIN = "main"; const std::string CBaseChainParams::TESTNET = "test"; +const std::string CBaseChainParams::SIGNET = "signet"; const std::string CBaseChainParams::REGTEST = "regtest"; void SetupChainParamsBaseOptions() @@ -23,6 +24,12 @@ void SetupChainParamsBaseOptions() gArgs.AddArg("-segwitheight=", "Set the activation height of segwit. -1 to disable. (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-signet", "Use the signet chain. Note that the network is defined by the signet_blockscript parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-signet_blockscript", "Blocks must satisfy the given script to be considered valid (only for signet networks)", ArgsManager::ALLOW_STRING, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-signet_genesisnonce", "Nonce value for use in genesis block to satisfy proof of work requirement", ArgsManager::ALLOW_INT, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-signet_hrp", "Human readable part of bech32 address (suffixed by \"sb\"; default = \"\")", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-signet_enforcescript", "Blocks must satisfy the given script to be considered valid (this replaces -signet_blockscript, and is used for opt-in-reorg mode)", ArgsManager::ALLOW_STRING, OptionsCategory::CHAINPARAMS); + gArgs.AddArg("-signet_seednode", "Specify a seed node for the signet network (may be used multiple times to specify multiple seed nodes)", ArgsManager::ALLOW_STRING, OptionsCategory::CHAINPARAMS); } static std::unique_ptr globalChainBaseParams; @@ -41,8 +48,9 @@ std::unique_ptr CreateBaseChainParams(const std::string& chain return MakeUnique("testnet3", 18332); else if (chain == CBaseChainParams::REGTEST) return MakeUnique("regtest", 18443); - else - throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); + else if (chain == CBaseChainParams::SIGNET) + return MakeUnique("signet", 38332); + throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); } void SelectBaseParams(const std::string& chain) diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index 69fe2438f3af..bd9579539f7c 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -19,6 +19,7 @@ class CBaseChainParams /** Chain name strings */ static const std::string MAIN; static const std::string TESTNET; + static const std::string SIGNET; static const std::string REGTEST; ///@} diff --git a/src/consensus/params.h b/src/consensus/params.h index e191fd6d26bd..ef149d9daca6 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -80,6 +80,12 @@ struct Params { int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; } uint256 nMinimumChainWork; uint256 defaultAssumeValid; + + /** + * If true, witness commitments contain a payload equal to a Bitcoin Script solution + * to a signet challenge as defined in the chain params. + */ + bool signet_blocks{false}; }; } // namespace Consensus diff --git a/src/core_read.cpp b/src/core_read.cpp index a3c9cf0159b3..de6c751789f7 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -158,10 +158,27 @@ bool DecodeHexBlockHeader(CBlockHeader& header, const std::string& hex_header) return true; } +inline bool ReadFromDisk(const std::string& path, std::string& output) +{ + FILE* fp = fopen(path.c_str(), "rb"); + if (!fp) return false; + char buf[1025]; + size_t r; + buf[1024] = 0; + output = ""; + while (0 < (r = fread(buf, 1, 1024, fp))) { buf[r] = 0; output += buf; } + while (output.size() > 0 && output[output.size() - 1] == '\n') output = output.substr(0, output.size() - 1); + fclose(fp); + return true; +} + bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk) { - if (!IsHex(strHexBlk)) + if (!IsHex(strHexBlk)) { + std::string actual; + if (ReadFromDisk(strHexBlk, actual)) return DecodeHexBlk(block, actual); return false; + } std::vector blockData(ParseHex(strHexBlk)); CDataStream ssBlock(blockData, SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/primitives/block.cpp b/src/primitives/block.cpp index 0c84ed6da22e..98c93e986219 100644 --- a/src/primitives/block.cpp +++ b/src/primitives/block.cpp @@ -28,3 +28,65 @@ std::string CBlock::ToString() const } return s.str(); } + +bool CBlock::GetWitnessCommitmentSection(const uint8_t header[4], std::vector& result) const +{ + int cidx = GetWitnessCommitmentIndex(); + if (cidx == -1) return false; + auto script = vtx.at(0)->vout.at(cidx).scriptPubKey; + opcodetype opcode; + CScript::const_iterator pc = script.begin(); + ++pc; // move beyond initial OP_RETURN + while (script.GetOp(pc, opcode, result)) { + if (result.size() > 3 && !memcmp(result.data(), header, 4)) { + result.erase(result.begin(), result.begin() + 4); + return true; + } + } + result.clear(); + return false; +} + +bool CBlock::SetWitnessCommitmentSection(CMutableTransaction& mtx, const uint8_t header[4], const std::vector& data) +{ + int cidx = GetWitnessCommitmentIndex(mtx); + if (cidx == -1) return false; + + CScript result; + std::vector pushdata; + auto script = mtx.vout[cidx].scriptPubKey; + opcodetype opcode; + CScript::const_iterator pc = script.begin(); + result.push_back(*pc++); + bool found = false; + while (script.GetOp(pc, opcode, pushdata)) { + if (pushdata.size() > 0) { + if (pushdata.size() > 3 && !memcmp(pushdata.data(), header, 4)) { + // replace pushdata + found = true; + pushdata.erase(pushdata.begin() + 4, pushdata.end()); + pushdata.insert(pushdata.end(), data.begin(), data.end()); + } + result << pushdata; + } else { + result << opcode; + } + } + if (!found) { + // append section as it did not exist + pushdata.clear(); + pushdata.insert(pushdata.end(), header, header + 4); + pushdata.insert(pushdata.end(), data.begin(), data.end()); + result << pushdata; + } + mtx.vout[cidx].scriptPubKey = result; + return true; +} + +bool CBlock::SetWitnessCommitmentSection(const uint8_t header[4], const std::vector& data) +{ + auto mtx = CMutableTransaction(*vtx[0]); + if (!SetWitnessCommitmentSection(mtx, header, data)) return false; + vtx[0] = std::make_shared(mtx); + return true; +} diff --git a/src/primitives/block.h b/src/primitives/block.h index 750d42efbc23..23a375556502 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -116,6 +116,50 @@ class CBlock : public CBlockHeader return block; } + /** + * Get the vout index of the segwit commitment in the coinbase transaction of this block. + * + * Returns -1 if no witness commitment was found. + */ + inline int GetWitnessCommitmentIndex() const + { + return vtx.empty() ? -1 : GetWitnessCommitmentIndex(*vtx.at(0)); + } + + /** + * Get the vout index of the segwit commitment in the given coinbase transaction. + * + * Returns -1 if no witness commitment was found. + */ + template static inline int GetWitnessCommitmentIndex(const T& coinbase) { + for (int64_t o = coinbase.vout.size() - 1; o > -1; --o) { + auto vospk = coinbase.vout[o].scriptPubKey; + if (vospk.size() >= 38 && vospk[0] == OP_RETURN && vospk[1] == 0x24 && vospk[2] == 0xaa && vospk[3] == 0x21 && vospk[4] == 0xa9 && vospk[5] == 0xed) { + return o; + } + } + return -1; + } + + /** + * Attempt to get the data for the section with the given header in the witness commitment of this block. + * + * Returns false if header was not found. The data (excluding the 4 byte header) is written into result if found. + */ + bool GetWitnessCommitmentSection(const uint8_t header[4], std::vector& result) const; + + /** + * Attempt to add or update the data for the section with the given header in the witness commitment of this block. + * + * This operation may fail and return false, if no witness commitment exists upon call time. Returns true on success. + */ + bool SetWitnessCommitmentSection(const uint8_t header[4], const std::vector& data); + + /** + * The tx based equivalent of the above. + */ + static bool SetWitnessCommitmentSection(CMutableTransaction& tx, const uint8_t header[4], const std::vector& data); + std::string ToString() const; }; diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index dcdb2479770f..7a8c677b2853 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -47,6 +47,7 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80; #define QAPP_APP_NAME_DEFAULT "Bitcoin-Qt" #define QAPP_APP_NAME_TESTNET "Bitcoin-Qt-testnet" #define QAPP_APP_NAME_REGTEST "Bitcoin-Qt-regtest" +#define QAPP_APP_NAME_SIGNET "Bitcoin-Qt-signet" /* One gigabyte (GB) in bytes */ static constexpr uint64_t GB_BYTES{1000000000}; diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp index 5c039a939eee..5a30bd165531 100644 --- a/src/qt/networkstyle.cpp +++ b/src/qt/networkstyle.cpp @@ -19,7 +19,8 @@ static const struct { } network_styles[] = { {"main", QAPP_APP_NAME_DEFAULT, 0, 0}, {"test", QAPP_APP_NAME_TESTNET, 70, 30}, - {"regtest", QAPP_APP_NAME_REGTEST, 160, 30} + {"regtest", QAPP_APP_NAME_REGTEST, 160, 30}, + {"signet", QAPP_APP_NAME_SIGNET, 35, 15}, }; static const unsigned network_styles_count = sizeof(network_styles)/sizeof(*network_styles); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 946152d9aa38..a7de495c488a 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -24,6 +24,7 @@ #include #include #include