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..927c60733e86 --- /dev/null +++ b/contrib/signet/addtxtoblock.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019-2020 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 ( # noqa: E402 + CBlock, + CTransaction, + FromHex, + ToHex, +) + +from test_framework.blocktools import add_witness_commitment # noqa: E402 + +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..62b86eb618c5 --- /dev/null +++ b/contrib/signet/args.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Copyright (c) 2019-2020 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 + 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' + 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-cli binary: $bcli"; exit 1; } +fi diff --git a/contrib/signet/forker.sh b/contrib/signet/forker.sh new file mode 100755 index 000000000000..e8d2c8d55107 --- /dev/null +++ b/contrib/signet/forker.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Copyright (c) 2019-2020 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/generate.py b/contrib/signet/generate.py new file mode 100755 index 000000000000..a4a8f2dc2ace --- /dev/null +++ b/contrib/signet/generate.py @@ -0,0 +1,683 @@ +#!/usr/bin/env python3 +# Copyright (c) 2010 ArtForz -- public domain half-a-node +# Copyright (c) 2012 Jeff Garzik +# Copyright (c) 2010-2020 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 copy +import hashlib +import json +import struct +import sys +import time +import subprocess + +from codecs import encode +from binascii import hexlify, unhexlify +from io import BytesIO + +MAX_BLOCK_BASE_SIZE = 1000000 + +COIN = 100000000 # 1 btc in satoshis +MAX_MONEY = "20999999.99999999" + +WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed" +SIGNET_HEADER = b"\xec\xc7\xda\xa2" + +def FromHex(obj, hex_string): + obj.deserialize(BytesIO(unhexlify(hex_string.encode('ascii')))) + return obj + +def ToHex(obj): + return obj.serialize().hex() + +# Serialization/deserialization tools +def sha256(s): + return hashlib.new('sha256', s).digest() + +def hash256(s): + return sha256(sha256(s)) + +def ser_compact_size(l): + r = b"" + if l < 253: + r = struct.pack("B", l) + elif l < 0x10000: + r = struct.pack(">= 32 + return rs + + +def uint256_from_str(s): + r = 0 + t = struct.unpack("> 24) & 0xFF + v = (c & 0xFFFFFF) << (8 * (nbytes - 3)) + return v + + +def deser_vector(f, c): + nit = deser_compact_size(f) + r = [] + for i in range(nit): + t = c() + t.deserialize(f) + r.append(t) + return r + + +# ser_function_name: Allow for an alternate serialization function on the +# entries in the vector (we use this for serializing the vector of transactions +# for a witness block). +def ser_vector(l, ser_function_name=None): + r = ser_compact_size(len(l)) + for i in l: + if ser_function_name: + r += getattr(i, ser_function_name)() + else: + r += i.serialize() + return r + + +def deser_uint256_vector(f): + nit = deser_compact_size(f) + r = [] + for i in range(nit): + t = deser_uint256(f) + r.append(t) + return r + + +def ser_uint256_vector(l): + r = ser_compact_size(len(l)) + for i in l: + r += ser_uint256(i) + return r + + +def deser_string_vector(f): + nit = deser_compact_size(f) + r = [] + for i in range(nit): + t = deser_string(f) + r.append(t) + return r + + +def ser_string_vector(l): + r = ser_compact_size(len(l)) + for sv in l: + r += ser_string(sv) + return r + + +# Objects that map to bitcoind objects, which can be serialized/deserialized + +class COutPoint: + __slots__ = ("hash", "n") + + def __init__(self, hash=0, n=0): + self.hash = hash + self.n = n + + def deserialize(self, f): + self.hash = deser_uint256(f) + self.n = struct.unpack(" 21000000 * COIN: + return False + return True + + def __repr__(self): + return "CTransaction(nVersion=%i vin=%s vout=%s wit=%s nLockTime=%i)" \ + % (self.nVersion, repr(self.vin), repr(self.vout), repr(self.wit), self.nLockTime) + + +class CBlockHeader: + __slots__ = ("hash", "hashMerkleRoot", "hashPrevBlock", "nBits", "nNonce", + "nTime", "nVersion", "sha256") + + def __init__(self, header=None): + if header is None: + self.set_null() + else: + self.nVersion = header.nVersion + self.hashPrevBlock = header.hashPrevBlock + self.hashMerkleRoot = header.hashMerkleRoot + self.nTime = header.nTime + self.nBits = header.nBits + self.nNonce = header.nNonce + self.sha256 = header.sha256 + self.hash = header.hash + self.calc_sha256() + + def set_null(self): + self.nVersion = 1 + self.hashPrevBlock = 0 + self.hashMerkleRoot = 0 + self.nTime = 0 + self.nBits = 0 + self.nNonce = 0 + self.sha256 = None + self.hash = None + + def deserialize(self, f): + self.nVersion = struct.unpack(" 1: + newhashes = [] + for i in range(0, len(hashes), 2): + i2 = min(i+1, len(hashes)-1) + newhashes.append(hash256(hashes[i] + hashes[i2])) + hashes = newhashes + return uint256_from_str(hashes[0]) + + def calc_merkle_root(self): + hashes = [] + for tx in self.vtx: + tx.calc_sha256() + hashes.append(ser_uint256(tx.sha256)) + return self.get_merkle_root(hashes) + + def calc_witness_merkle_root(self): + # For witness root purposes, the hash of the + # coinbase, with witness, is defined to be 0...0 + hashes = [ser_uint256(0)] + + for tx in self.vtx[1:]: + # Calculate the hashes with witness data + hashes.append(ser_uint256(tx.calc_sha256(True))) + + return self.get_merkle_root(hashes) + + def is_valid(self): + self.calc_sha256() + target = uint256_from_compact(self.nBits) + if self.sha256 > target: + return False + for tx in self.vtx: + if not tx.is_valid(): + return False + if self.calc_merkle_root() != self.hashMerkleRoot: + return False + return True + + def solve(self): + self.rehash() + target = uint256_from_compact(self.nBits) + while self.sha256 > target: + self.nNonce += 1 + self.rehash() + + def __repr__(self): + return "CBlock(nVersion=%i hashPrevBlock=%064x hashMerkleRoot=%064x nTime=%s nBits=%08x nNonce=%08x vtx=%s)" \ + % (self.nVersion, self.hashPrevBlock, self.hashMerkleRoot, + time.ctime(self.nTime), self.nBits, self.nNonce, repr(self.vtx)) + + +def script_BIP34_coinbase_height(height): + assert int(height) == height and height >= 0 + if height == 0: + return bytes([0x00, 0x51]) + elif height <= 16: + return bytes([0x50+height, 0x51]) + else: + b = bytearray(0) + while height > 0: + b.append(height & 0xff) + height >>= 8 + if b[-1] & 0x80: + b.append(0x00) + return bytes([len(b)]) + b + +def create_coinbase(height, value, spk): + cb = CTransaction() + cb.vin = [CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)] + cb.vout = [CTxOut(value, spk)] + return cb + +def pushdata(data): + assert 1 < len(data) < 65536 + l = len(data) + if l <= 75: + push = bytes([l]) + elif l <= 255: + push = bytes([76,l]) + elif l <= 65535: + push = bytes([77,l%256,l//256]) + else: + assert False + return push + data + +def get_witness_script(witness_root, witness_nonce): + commitment = uint256_from_str(hash256(ser_uint256(witness_root) + ser_uint256(witness_nonce))) + return b"\x6a" + pushdata(WITNESS_COMMITMENT_HEADER + ser_uint256(commitment)) + +def signet_txs(block, challenge): + # assumes signet solution has not been added yet so does not need + # to be removed + + txs = block.vtx[:] + txs[0] = CTransaction(txs[0]) + txs[0].vout[-1].scriptPubKey += pushdata(SIGNET_HEADER) + hashes = [] + for tx in txs: + tx.rehash() + hashes.append(ser_uint256(tx.sha256)) + mroot = block.get_merkle_root(hashes) + + sd = b"" + sd += struct.pack("&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..85ce4416694c --- /dev/null +++ b/contrib/signet/issuer.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Copyright (c) 2019-2020 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..ba69e1bbd407 --- /dev/null +++ b/contrib/signet/mkblock.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Copyright (c) 2019-2020 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..58858159ad16 --- /dev/null +++ b/contrib/signet/secondary.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Copyright (c) 2019-2020 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 cd3cc9570754..dbc3ffd4a251 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -200,6 +200,7 @@ BITCOIN_CORE_H = \ script/signingprovider.h \ script/standard.h \ shutdown.h \ + signet.h \ streams.h \ support/allocators/secure.h \ support/allocators/zeroafterfree.h \ @@ -320,6 +321,7 @@ libbitcoin_server_a_SOURCES = \ rpc/server.cpp \ script/sigcache.cpp \ shutdown.cpp \ + signet.cpp \ timedata.cpp \ torcontrol.cpp \ txdb.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index a7c9e33f0733..03c860810c26 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -7,6 +7,7 @@ #include #include +#include // for signet block challenge hash #include #include #include @@ -63,6 +64,8 @@ class CMainParams : public CChainParams { public: CMainParams() { strNetworkID = CBaseChainParams::MAIN; + consensus.signet_blocks = false; + consensus.signet_challenge.clear(); consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22"); consensus.BIP34Height = 227931; @@ -172,6 +175,8 @@ class CTestNetParams : public CChainParams { public: CTestNetParams() { strNetworkID = CBaseChainParams::TESTNET; + consensus.signet_blocks = false; + consensus.signet_challenge.clear(); consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105"); consensus.BIP34Height = 21111; @@ -250,6 +255,92 @@ class CTestNetParams : public CChainParams { } }; +/** + * Signet + */ +class SigNetParams : public CChainParams { +public: + explicit SigNetParams(const ArgsManager& args) { + std::vector bin; + vSeeds.clear(); + + if (!args.IsArgSet("-signet_blockscript")) { + LogPrintf("Using default signet network\n"); + bin = ParseHex("512103ad5e0edad18cb1f0fc0d28a3d4f1f3e445640337489abb10404f2d1e086be43051ae"); + vSeeds.emplace_back("178.128.221.177"); + vSeeds.emplace_back("2a01:7c8:d005:390::5"); + vSeeds.emplace_back("ntv3mtqw5wt63red.onion:38333"); + } else { + const auto blockscript = args.GetArgs("-signet_blockscript"); + if (blockscript.size() != 1) { + throw std::runtime_error(strprintf("%s: -signet_blockscript cannot be multiple values.", __func__)); + } + bin = ParseHex(blockscript[0]); + + LogPrintf("Signet with block script %s\n", blockscript[0]); + } + + if (args.IsArgSet("-signet_seednode")) { + vSeeds = gArgs.GetArgs("-signet_seednode"); + } + + strNetworkID = CBaseChainParams::SIGNET; + consensus.signet_blocks = true; + consensus.signet_challenge.assign(bin.begin(), bin.end()); + consensus.nSubsidyHalvingInterval = 210000; + consensus.BIP34Height = 1; + consensus.BIP65Height = 1; + consensus.BIP66Height = 1; + consensus.CSVHeight = 1; + consensus.SegwitHeight = 1; + 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; + + // message start is defined as the first 4 bytes of the sha256d of the block script + CHashWriter h(SER_DISK, 0); + h << consensus.signet_challenge; + uint256 hash = h.GetHash(); + memcpy(pchMessageStart, hash.begin(), 4); + + nDefaultPort = 38333; + nPruneAfterHeight = 1000; + m_assumed_blockchain_size = 0; + m_assumed_chain_state_size = 0; + + genesis = CreateGenesisBlock(1534313275, 100123, 0x1e2adc28, 1, 50 * COIN); + consensus.hashGenesisBlock = genesis.GetHash(); + + 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"; + + fDefaultConsistencyChecks = false; + fRequireStandard = true; + m_is_test_chain = true; + m_is_mockable_chain = false; + + chainTxData = ChainTxData{ + 0, + 0, + 0 + }; + } +}; + /** * Regression test */ @@ -257,6 +348,8 @@ class CRegTestParams : public CChainParams { public: explicit CRegTestParams(const ArgsManager& args) { strNetworkID = CBaseChainParams::REGTEST; + consensus.signet_blocks = false; + consensus.signet_challenge.clear(); consensus.nSubsidyHalvingInterval = 150; consensus.BIP16Exception = uint256(); consensus.BIP34Height = 500; // BIP34 activated on regtest (Used in functional tests) @@ -395,6 +488,9 @@ std::unique_ptr CreateChainParams(const std::string& chain) return std::unique_ptr(new CMainParams()); else if (chain == CBaseChainParams::TESTNET) return std::unique_ptr(new CTestNetParams()); + else if (chain == CBaseChainParams::SIGNET) { + return std::unique_ptr(new SigNetParams(gArgs)); + } else if (chain == CBaseChainParams::REGTEST) return std::unique_ptr(new CRegTestParams(gArgs)); throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 1825ced64051..ec73a1598d52 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(ArgsManager& argsman) @@ -23,6 +24,9 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-segwitheight=", "Set the activation height of segwit. -1 to disable. (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.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); + argsman.AddArg("-signet", "Use the signet chain. Note that the network is defined by the signet_blockscript parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-signet_blockscript", "Blocks must satisfy the given script to be considered valid (only for signet networks)", ArgsManager::ALLOW_STRING, OptionsCategory::CHAINPARAMS); + argsman.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; @@ -39,10 +43,11 @@ std::unique_ptr CreateBaseChainParams(const std::string& chain return MakeUnique("", 8332); else if (chain == CBaseChainParams::TESTNET) return MakeUnique("testnet3", 18332); + else if (chain == CBaseChainParams::SIGNET) + return MakeUnique("signet", 38332); else if (chain == CBaseChainParams::REGTEST) return MakeUnique("regtest", 18443); - else - throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); + 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 1c52d0ea97b5..9852446b3c34 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -21,6 +21,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 61b1fbc2e5a8..1c76fb168b43 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -80,6 +80,13 @@ 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 the signet challenge. + */ + bool signet_blocks{false}; + std::vector signet_challenge; }; } // namespace Consensus diff --git a/src/consensus/validation.h b/src/consensus/validation.h index 8de7a8f2d82d..e7d80d081faf 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -150,4 +150,34 @@ static inline int64_t GetTransactionInputWeight(const CTxIn& txin) return ::GetSerializeSize(txin, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(txin, PROTOCOL_VERSION) + ::GetSerializeSize(txin.scriptWitness.stack, PROTOCOL_VERSION); } +/** Index marker for when no witness commitment is present in a coinbase transaction. */ +static constexpr int NO_WITNESS_COMMITMENT{-1}; +/** Minimum size of a witness commitment structure. Defined in BIP 141. **/ +static constexpr size_t MINIMUM_WITNESS_COMMITMENT{38}; + +/** Compute at which vout of the given coinbase transaction the witness commitment occurs, or -1 if not found */ +template int GetWitnessCommitmentIndex(const T& tx) +{ + int commitpos = NO_WITNESS_COMMITMENT; + for (size_t o = 0; o < tx.vout.size(); o++) { + const CTxOut& vout = tx.vout[o]; + if (vout.scriptPubKey.size() >= MINIMUM_WITNESS_COMMITMENT && + vout.scriptPubKey[0] == OP_RETURN && + vout.scriptPubKey[1] == 0x24 && + vout.scriptPubKey[2] == 0xaa && + vout.scriptPubKey[3] == 0x21 && + vout.scriptPubKey[4] == 0xa9 && + vout.scriptPubKey[5] == 0xed) { + commitpos = o; + } + } + return commitpos; +} + +/** Compute at which vout of the block's coinbase transaction the witness commitment occurs, or -1 if not found */ +static inline int GetWitnessCommitmentIndex(const CBlock& block) +{ + return block.vtx.empty() ? NO_WITNESS_COMMITMENT : GetWitnessCommitmentIndex(*block.vtx[0]); +} + #endif // BITCOIN_CONSENSUS_VALIDATION_H diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 9457ea37d6cf..882d2c8f52d9 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -46,6 +46,7 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80; #define QAPP_ORG_DOMAIN "bitcoin.org" #define QAPP_APP_NAME_DEFAULT "Bitcoin-Qt" #define QAPP_APP_NAME_TESTNET "Bitcoin-Qt-testnet" +#define QAPP_APP_NAME_SIGNET "Bitcoin-Qt-signet" #define QAPP_APP_NAME_REGTEST "Bitcoin-Qt-regtest" /* One gigabyte (GB) in bytes */ diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp index 3a251e057347..b1081f6aee8c 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} + {"signet", QAPP_APP_NAME_SIGNET, 35, 15}, + {"regtest", QAPP_APP_NAME_REGTEST, 160, 30}, }; 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 f27373b57cc4..4c378645d96e 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1278,6 +1278,9 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) } const Consensus::Params& consensusParams = Params().GetConsensus(); + if (consensusParams.signet_blocks) { + obj.pushKV("signet_blockscript", HexStr(consensusParams.signet_challenge)); + } UniValue softforks(UniValue::VOBJ); BuriedForkDescPushBack(softforks, "bip34", consensusParams.BIP34Height); BuriedForkDescPushBack(softforks, "bip66", consensusParams.BIP66Height); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index fee6a893eb79..11dc66116811 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -697,6 +697,13 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) // TODO: Maybe recheck connections/IBD and (if something wrong) send an expires-immediately template to stop miners? } + const Consensus::Params& consensusParams = Params().GetConsensus(); + + // GBT must be called with 'signet' set in the rules for signet chains + if (consensusParams.signet_blocks && setClientRules.count("signet") != 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the signet rule set (call with {\"rules\": [\"segwit\", \"signet\"]})"); + } + // GBT must be called with 'segwit' set in the rules if (setClientRules.count("segwit") != 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the segwit rule set (call with {\"rules\": [\"segwit\"]})"); @@ -728,7 +735,6 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) } CHECK_NONFATAL(pindexPrev); CBlock* pblock = &pblocktemplate->block; // pointer for convenience - const Consensus::Params& consensusParams = Params().GetConsensus(); // Update nTime UpdateTime(pblock, consensusParams, pindexPrev); @@ -792,6 +798,10 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) UniValue aRules(UniValue::VARR); aRules.push_back("csv"); if (!fPreSegWit) aRules.push_back("!segwit"); + if (consensusParams.signet_blocks) { + aRules.push_back("!signet"); + } + UniValue vbavailable(UniValue::VOBJ); for (int j = 0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { Consensus::DeploymentPos pos = Consensus::DeploymentPos(j); @@ -872,6 +882,10 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) result.pushKV("bits", strprintf("%08x", pblock->nBits)); result.pushKV("height", (int64_t)(pindexPrev->nHeight+1)); + if (consensusParams.signet_blocks) { + result.pushKV("signet_challenge", HexStr(consensusParams.signet_challenge)); + } + if (!pblocktemplate->vchCoinbaseCommitment.empty()) { result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment)); } diff --git a/src/signet.cpp b/src/signet.cpp new file mode 100644 index 000000000000..efee8b020c26 --- /dev/null +++ b/src/signet.cpp @@ -0,0 +1,147 @@ +// Copyright (c) 2019-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include