Auto merge of #4256 - str4d:zip-213-shielded-coinbase, r=daira

[NU3 Heartwood] Shielded Coinbase

Implements [ZIP 213](https://github.com/zcash/zips/pull/217).
This commit is contained in:
Homu 2020-03-06 10:19:13 +00:00
commit 35bff6ac7c
23 changed files with 784 additions and 163 deletions

View File

@ -81,6 +81,7 @@ testScripts=(
'shorter_block_times.py'
'sprout_sapling_migration.py'
'turnstile.py'
'mining_shielded_coinbase.py'
);
testScriptsExt=(
'getblocktemplate_longpoll.py'

View File

@ -0,0 +1,129 @@
#!/usr/bin/env python3
# Copyright (c) 2019 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
from decimal import Decimal
from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises,
bitcoind_processes,
connect_nodes,
initialize_chain_clean,
start_node,
wait_and_assert_operationid_status,
)
class ShieldCoinbaseTest (BitcoinTestFramework):
def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4)
def start_node_with(self, index, extra_args=[]):
args = [
"-nuparams=2bb40e60:1", # Blossom
"-nuparams=f5b9230b:10", # Heartwood
"-nurejectoldversions=false",
]
return start_node(index, self.options.tmpdir, args + extra_args)
def setup_network(self, split=False):
self.nodes = []
self.nodes.append(self.start_node_with(0))
self.nodes.append(self.start_node_with(1))
connect_nodes(self.nodes[1], 0)
self.is_network_split=False
self.sync_all()
def run_test (self):
# Generate a Sapling address for node 1
node1_zaddr = self.nodes[1].z_getnewaddress('sapling')
self.nodes[1].stop()
bitcoind_processes[1].wait()
self.nodes[1] = self.start_node_with(1, [
"-mineraddress=%s" % node1_zaddr,
])
connect_nodes(self.nodes[1], 0)
# Node 0 can mine blocks, because it is targeting a transparent address
print("Mining block with node 0")
self.nodes[0].generate(1)
self.sync_all()
walletinfo = self.nodes[0].getwalletinfo()
assert_equal(walletinfo['immature_balance'], 5)
assert_equal(walletinfo['balance'], 0)
# Node 1 cannot mine blocks, because it is targeting a Sapling address
# but Heartwood is not yet active
print("Attempting to mine block with node 1")
assert_raises(JSONRPCException, self.nodes[1].generate, 1)
assert_equal(self.nodes[1].z_getbalance(node1_zaddr, 0), 0)
assert_equal(self.nodes[1].z_getbalance(node1_zaddr), 0)
# Stop node 1 and check logs to verify the block was rejected correctly
print("Checking node 1 logs")
self.nodes[1].stop()
bitcoind_processes[1].wait()
logpath = self.options.tmpdir + "/node1/regtest/debug.log"
foundErrorMsg = False
with open(logpath, "r") as myfile:
logdata = myfile.readlines()
for logline in logdata:
if "CheckTransaction(): coinbase has output descriptions" in logline:
foundErrorMsg = True
break
assert(foundErrorMsg)
# Restart node 1
self.nodes[1] = self.start_node_with(1, [
"-mineraddress=%s" % node1_zaddr,
])
connect_nodes(self.nodes[1], 0)
# Activate Heartwood
print("Activating Heartwood")
self.nodes[0].generate(8)
self.sync_all()
# Node 1 can now mine blocks!
print("Mining block with node 1")
self.nodes[1].generate(1)
self.sync_all()
# Transparent coinbase outputs are subject to coinbase maturity
assert_equal(self.nodes[0].getbalance(), Decimal('0'))
assert_equal(self.nodes[0].z_gettotalbalance()['transparent'], '0.00')
assert_equal(self.nodes[0].z_gettotalbalance()['private'], '0.00')
assert_equal(self.nodes[0].z_gettotalbalance()['total'], '0.00')
# Shielded coinbase outputs are not subject to coinbase maturity
assert_equal(self.nodes[1].z_getbalance(node1_zaddr, 0), 5)
assert_equal(self.nodes[1].z_getbalance(node1_zaddr), 5)
assert_equal(self.nodes[1].z_gettotalbalance()['private'], '5.00')
assert_equal(self.nodes[1].z_gettotalbalance()['total'], '5.00')
# Send from Sapling coinbase to Sapling address and transparent address
# (to check that a non-empty vout is allowed when spending shielded
# coinbase)
print("Sending Sapling coinbase to Sapling address")
node0_zaddr = self.nodes[0].z_getnewaddress('sapling')
node0_taddr = self.nodes[0].getnewaddress()
recipients = []
recipients.append({"address": node0_zaddr, "amount": Decimal('2')})
recipients.append({"address": node0_taddr, "amount": Decimal('2')})
myopid = self.nodes[1].z_sendmany(node1_zaddr, recipients, 1, 0)
wait_and_assert_operationid_status(self.nodes[1], myopid)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
assert_equal(self.nodes[0].z_getbalance(node0_zaddr), 2)
assert_equal(self.nodes[0].z_getbalance(node0_taddr), 2)
assert_equal(self.nodes[1].z_getbalance(node1_zaddr), 1)
if __name__ == '__main__':
ShieldCoinbaseTest().main()

View File

@ -5,8 +5,11 @@
#include "main.h"
#include "primitives/transaction.h"
#include "consensus/validation.h"
#include "transaction_builder.h"
#include "utiltest.h"
#include <librustzcash.h>
extern ZCJoinSplit* params;
TEST(ChecktransactionTests, CheckVpubNotBothNonzero) {
@ -1027,3 +1030,191 @@ TEST(ChecktransactionTests, BadTxReceivedOverNetwork)
}
}
}
TEST(CheckTransaction, InvalidShieldedCoinbase) {
RegtestActivateSapling();
CMutableTransaction mtx = GetValidTransaction();
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
// Make it an invalid shielded coinbase (no ciphertexts or commitments).
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
mtx.vShieldedOutput.resize(1);
mtx.vJoinSplit.resize(0);
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
// Before Heartwood, output descriptions are rejected.
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-has-output-description", false)).Times(1);
ContextualCheckTransaction(tx, state, Params(), 10, 57);
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
// From Heartwood, the output description is allowed but invalid (undecryptable).
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-output-desc-invalid-outct", false)).Times(1);
ContextualCheckTransaction(tx, state, Params(), 10, 57);
RegtestDeactivateHeartwood();
}
TEST(CheckTransaction, HeartwoodAcceptsShieldedCoinbase) {
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
auto chainparams = Params();
uint256 ovk;
auto note = libzcash::SaplingNote(
libzcash::SaplingSpendingKey::random().default_address(), CAmount(123456));
auto output = OutputDescriptionInfo(ovk, note, {{0xF6}});
auto ctx = librustzcash_sapling_proving_ctx_init();
auto odesc = output.Build(ctx).get();
librustzcash_sapling_proving_ctx_free(ctx);
CMutableTransaction mtx = GetValidTransaction();
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
mtx.vJoinSplit.resize(0);
mtx.vShieldedOutput.push_back(odesc);
// Transaction should fail with a bad public cmu.
{
auto cmOrig = mtx.vShieldedOutput[0].cmu;
mtx.vShieldedOutput[0].cmu = uint256S("1234");
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-output-desc-invalid-outct", false)).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
mtx.vShieldedOutput[0].cmu = cmOrig;
}
// Transaction should fail with a bad outCiphertext.
{
auto outCtOrig = mtx.vShieldedOutput[0].outCiphertext;
mtx.vShieldedOutput[0].outCiphertext = {{}};
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-output-desc-invalid-outct", false)).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
mtx.vShieldedOutput[0].outCiphertext = outCtOrig;
}
// Transaction should fail with a bad encCiphertext.
{
auto encCtOrig = mtx.vShieldedOutput[0].encCiphertext;
mtx.vShieldedOutput[0].encCiphertext = {{}};
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-cb-output-desc-invalid-encct", false)).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
mtx.vShieldedOutput[0].encCiphertext = encCtOrig;
}
// Test the success case. The unmodified transaction should fail signature
// validation, which is an unrelated consensus rule and is checked after the
// shielded coinbase rules have passed.
{
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid", false)).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
}
RegtestDeactivateHeartwood();
}
// Check that the consensus rules relevant to valueBalance, vShieldedOutput, and
// bindingSig from https://zips.z.cash/protocol/protocol.pdf#txnencoding are
// applied to coinbase transactions.
TEST(CheckTransaction, HeartwoodEnforcesSaplingRulesOnShieldedCoinbase) {
RegtestActivateHeartwood(false, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
auto chainparams = Params();
uint256 ovk;
auto note = libzcash::SaplingNote(
libzcash::SaplingSpendingKey::random().default_address(), CAmount(123456));
auto output = OutputDescriptionInfo(ovk, note, {{0xF6}});
CMutableTransaction mtx = GetValidTransaction();
mtx.fOverwintered = true;
mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID;
mtx.nVersion = SAPLING_TX_VERSION;
mtx.vin.resize(1);
mtx.vin[0].prevout.SetNull();
mtx.vin[0].scriptSig << 123;
mtx.vJoinSplit.resize(0);
mtx.valueBalance = -1000;
// Coinbase transaction should fail non-contextual checks with no shielded
// outputs and non-zero valueBalance.
{
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-nonzero", false)).Times(1);
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
}
// Add a Sapling output.
auto ctx = librustzcash_sapling_proving_ctx_init();
auto odesc = output.Build(ctx).get();
librustzcash_sapling_proving_ctx_free(ctx);
mtx.vShieldedOutput.push_back(odesc);
// Coinbase transaction should fail non-contextual checks with valueBalance
// out of range.
{
mtx.valueBalance = MAX_MONEY + 1;
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false)).Times(1);
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
}
{
mtx.valueBalance = -MAX_MONEY - 1;
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false)).Times(1);
EXPECT_FALSE(CheckTransactionWithoutProofVerification(tx, state));
}
mtx.valueBalance = -1000;
CTransaction tx(mtx);
EXPECT_TRUE(tx.IsCoinBase());
// Coinbase transaction should now pass non-contextual checks.
MockCValidationState state;
EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state));
// Coinbase transaction does not pass contextual checks, as bindingSig
// consensus rule is enforced.
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid", false)).Times(1);
ContextualCheckTransaction(tx, state, chainparams, 10, 57);
RegtestDeactivateHeartwood();
}

View File

@ -7,37 +7,37 @@
#include "util.h"
TEST(Miner, GetScriptForMinerAddress) {
TEST(Miner, GetMinerAddress) {
SelectParams(CBaseChainParams::MAIN);
// No miner address set
{
boost::shared_ptr<CReserveScript> coinbaseScript;
GetScriptForMinerAddress(coinbaseScript);
EXPECT_FALSE((bool) coinbaseScript);
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_FALSE(IsValidMinerAddress(minerAddress));
}
mapArgs["-mineraddress"] = "notAnAddress";
{
boost::shared_ptr<CReserveScript> coinbaseScript;
GetScriptForMinerAddress(coinbaseScript);
EXPECT_FALSE((bool) coinbaseScript);
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_FALSE(IsValidMinerAddress(minerAddress));
}
// Partial address
// Partial transparent address
mapArgs["-mineraddress"] = "t1T8yaLVhNqxA5KJcmiqq";
{
boost::shared_ptr<CReserveScript> coinbaseScript;
GetScriptForMinerAddress(coinbaseScript);
EXPECT_FALSE((bool) coinbaseScript);
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_FALSE(IsValidMinerAddress(minerAddress));
}
// Typo in address
// Typo in transparent address
mapArgs["-mineraddress"] = "t1TByaLVhNqxA5KJcmiqqFN88e8DNp2PBfF";
{
boost::shared_ptr<CReserveScript> coinbaseScript;
GetScriptForMinerAddress(coinbaseScript);
EXPECT_FALSE((bool) coinbaseScript);
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_FALSE(IsValidMinerAddress(minerAddress));
}
// Set up expected scriptPubKey for t1T8yaLVhNqxA5KJcmiqqFN88e8DNp2PBfF
@ -45,30 +45,77 @@ TEST(Miner, GetScriptForMinerAddress) {
keyID.SetHex("eb88f1c65b39a823479ac9c7db2f4a865960a165");
CScript expectedCoinbaseScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
// Valid address
// Valid transparent address
mapArgs["-mineraddress"] = "t1T8yaLVhNqxA5KJcmiqqFN88e8DNp2PBfF";
{
boost::shared_ptr<CReserveScript> coinbaseScript;
GetScriptForMinerAddress(coinbaseScript);
EXPECT_TRUE((bool) coinbaseScript);
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_TRUE(IsValidMinerAddress(minerAddress));
EXPECT_TRUE(boost::get<boost::shared_ptr<CReserveScript>>(&minerAddress) != nullptr);
auto coinbaseScript = boost::get<boost::shared_ptr<CReserveScript>>(minerAddress);
EXPECT_EQ(expectedCoinbaseScript, coinbaseScript->reserveScript);
}
// Valid address with leading whitespace
// Valid transparent address with leading whitespace
mapArgs["-mineraddress"] = " t1T8yaLVhNqxA5KJcmiqqFN88e8DNp2PBfF";
{
boost::shared_ptr<CReserveScript> coinbaseScript;
GetScriptForMinerAddress(coinbaseScript);
EXPECT_TRUE((bool) coinbaseScript);
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_TRUE(IsValidMinerAddress(minerAddress));
EXPECT_TRUE(boost::get<boost::shared_ptr<CReserveScript>>(&minerAddress) != nullptr);
auto coinbaseScript = boost::get<boost::shared_ptr<CReserveScript>>(minerAddress);
EXPECT_EQ(expectedCoinbaseScript, coinbaseScript->reserveScript);
}
// Valid address with trailing whitespace
// Valid transparent address with trailing whitespace
mapArgs["-mineraddress"] = "t1T8yaLVhNqxA5KJcmiqqFN88e8DNp2PBfF ";
{
boost::shared_ptr<CReserveScript> coinbaseScript;
GetScriptForMinerAddress(coinbaseScript);
EXPECT_TRUE((bool) coinbaseScript);
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_TRUE(IsValidMinerAddress(minerAddress));
EXPECT_TRUE(boost::get<boost::shared_ptr<CReserveScript>>(&minerAddress) != nullptr);
auto coinbaseScript = boost::get<boost::shared_ptr<CReserveScript>>(minerAddress);
EXPECT_EQ(expectedCoinbaseScript, coinbaseScript->reserveScript);
}
// Partial Sapling address
mapArgs["-mineraddress"] = "zs1z7rejlpsa98s2rrrfkwmaxu53";
{
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_FALSE(IsValidMinerAddress(minerAddress));
}
// Typo in Sapling address
mapArgs["-mineraddress"] = "zs1s7rejlpsa98s2rrrfkwmaxu53e4ue0ulcrw0h4x5g8jl04tak0d3mm47vdtahatqrlkngh9slya";
{
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_FALSE(IsValidMinerAddress(minerAddress));
}
// Valid Sapling address
mapArgs["-mineraddress"] = "zs1z7rejlpsa98s2rrrfkwmaxu53e4ue0ulcrw0h4x5g8jl04tak0d3mm47vdtahatqrlkngh9slya";
{
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_TRUE(IsValidMinerAddress(minerAddress));
EXPECT_TRUE(boost::get<libzcash::SaplingPaymentAddress>(&minerAddress) != nullptr);
}
// Valid Sapling address with leading whitespace
mapArgs["-mineraddress"] = " zs1z7rejlpsa98s2rrrfkwmaxu53e4ue0ulcrw0h4x5g8jl04tak0d3mm47vdtahatqrlkngh9slya";
{
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_FALSE(IsValidMinerAddress(minerAddress));
}
// Valid Sapling address with trailing whitespace
mapArgs["-mineraddress"] = "zs1z7rejlpsa98s2rrrfkwmaxu53e4ue0ulcrw0h4x5g8jl04tak0d3mm47vdtahatqrlkngh9slya ";
{
MinerAddress minerAddress;
GetMinerAddress(minerAddress);
EXPECT_FALSE(IsValidMinerAddress(minerAddress));
}
}

View File

@ -94,7 +94,7 @@ TEST(TransactionBuilder, TransparentToSapling)
// Create a shielding transaction from transparent to Sapling
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000);
builder.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
auto tx = builder.Build().GetTxOrThrow();

View File

@ -437,6 +437,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-fuzzmessagestest=<n>", "Randomly fuzz 1 of every <n> network messages");
strUsage += HelpMessageOpt("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT));
strUsage += HelpMessageOpt("-nuparams=hexBranchId:activationHeight", "Use given activation height for specified network upgrade (regtest-only)");
strUsage += HelpMessageOpt("-nurejectoldversions", strprintf("Reject peers that don't know about the current epoch (regtest-only) (default: %u)", DEFAULT_NU_REJECT_OLD_VERSIONS));
}
string debugCategories = "addrman, alert, bench, coindb, db, estimatefee, http, libevent, lock, mempool, net, partitioncheck, pow, proxy, prune, "
"rand, reindex, rpc, selectcoins, tor, zmq, zrpc, zrpcunsafe (implies zrpc)"; // Don't translate these
@ -1037,9 +1038,15 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
if (mapArgs.count("-mineraddress")) {
CTxDestination addr = DecodeDestination(mapArgs["-mineraddress"]);
if (!IsValidDestination(addr)) {
return InitError(strprintf(
_("Invalid address for -mineraddress=<addr>: '%s' (must be a transparent address)"),
mapArgs["-mineraddress"]));
// Try a Sapling address
auto zaddr = DecodePaymentAddress(mapArgs["-mineraddress"]);
if (!IsValidPaymentAddress(zaddr) ||
boost::get<libzcash::SaplingPaymentAddress>(&zaddr) == nullptr)
{
return InitError(strprintf(
_("Invalid address for -mineraddress=<addr>: '%s' (must be a Sapling or transparent address)"),
mapArgs["-mineraddress"]));
}
}
}
#endif
@ -1077,6 +1084,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
}
}
if (mapArgs.count("-nurejectoldversions")) {
if (Params().NetworkIDString() != "regtest") {
return InitError("-nurejectoldversions may only be set on regtest.");
}
}
// ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log
// Initialize libsodium
@ -1517,10 +1530,15 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
#ifdef ENABLE_WALLET
bool minerAddressInLocalWallet = false;
if (pwalletMain) {
// Address has already been validated
CTxDestination addr = DecodeDestination(mapArgs["-mineraddress"]);
CKeyID keyID = boost::get<CKeyID>(addr);
minerAddressInLocalWallet = pwalletMain->HaveKey(keyID);
if (IsValidDestination(addr)) {
CKeyID keyID = boost::get<CKeyID>(addr);
minerAddressInLocalWallet = pwalletMain->HaveKey(keyID);
} else {
auto zaddr = DecodePaymentAddress(mapArgs["-mineraddress"]);
minerAddressInLocalWallet = boost::apply_visitor(
HaveSpendingKeyForPaymentAddress(pwalletMain), zaddr);
}
}
if (GetBoolArg("-minetolocalwallet", true) && !minerAddressInLocalWallet) {
return InitError(_("-mineraddress is not in the local wallet. Either use a local address, or set -minetolocalwallet=0"));
@ -1529,19 +1547,20 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
// This is leveraging the fact that boost::signals2 executes connected
// handlers in-order. Further up, the wallet is connected to this signal
// if the wallet is enabled. The wallet's ScriptForMining handler does
// nothing if -mineraddress is set, and GetScriptForMinerAddress() does
// nothing if -mineraddress is not set (or set to an invalid address).
// if the wallet is enabled. The wallet's AddressForMining handler does
// nothing if -mineraddress is set, and GetMinerAddress() does nothing
// if -mineraddress is not set (or set to an address that is not valid
// for mining).
//
// The upshot is that when ScriptForMining(script) is called:
// The upshot is that when AddressForMining(address) is called:
// - If -mineraddress is set (whether or not the wallet is enabled), the
// CScript argument is set to -mineraddress.
// - If the wallet is enabled and -mineraddress is not set, the CScript
// argument is set to a wallet address.
// - If the wallet is disabled and -mineraddress is not set, the CScript
// argument is set to -mineraddress.
// - If the wallet is enabled and -mineraddress is not set, the argument
// is set to a wallet address.
// - If the wallet is disabled and -mineraddress is not set, the
// argument is not modified; in practice this means it is empty, and
// GenerateBitcoins() returns an error.
GetMainSignals().ScriptForMining.connect(GetScriptForMinerAddress);
GetMainSignals().AddressForMining.connect(GetMinerAddress);
}
#endif // ENABLE_MINING

View File

@ -791,6 +791,7 @@ bool ContextualCheckTransaction(
bool overwinterActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_OVERWINTER);
bool saplingActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_SAPLING);
bool isSprout = !overwinterActive;
bool heartwoodActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_HEARTWOOD);
// If Sprout rules apply, reject transactions which are intended for Overwinter and beyond
if (isSprout && tx.fOverwintered) {
@ -887,6 +888,54 @@ bool ContextualCheckTransaction(
REJECT_INVALID, "bad-txns-oversize");
}
// Rules that apply before Heartwood:
if (!heartwoodActive) {
if (tx.IsCoinBase()) {
// A coinbase transaction cannot have output descriptions
if (tx.vShieldedOutput.size() > 0)
return state.DoS(
dosLevelPotentiallyRelaxing,
error("CheckTransaction(): coinbase has output descriptions"),
REJECT_INVALID, "bad-cb-has-output-description");
}
}
// Rules that apply to Heartwood or later:
if (heartwoodActive) {
if (tx.IsCoinBase()) {
// All Sapling outputs in coinbase transactions MUST have valid note commitments
// when recovered using a 32-byte array of zeroes as the outgoing viewing key.
// https://zips.z.cash/zip-0213#specification
uint256 ovk;
for (const OutputDescription &output : tx.vShieldedOutput) {
auto outPlaintext = SaplingOutgoingPlaintext::decrypt(
output.outCiphertext, ovk, output.cv, output.cmu, output.ephemeralKey);
if (!outPlaintext) {
return state.DoS(
DOS_LEVEL_BLOCK,
error("CheckTransaction(): coinbase output description has invalid outCiphertext"),
REJECT_INVALID,
"bad-cb-output-desc-invalid-outct");
}
// SaplingNotePlaintext::decrypt() checks note commitment validity.
if (!SaplingNotePlaintext::decrypt(
output.encCiphertext,
output.ephemeralKey,
outPlaintext->esk,
outPlaintext->pk_d,
output.cmu)
) {
return state.DoS(
DOS_LEVEL_BLOCK,
error("CheckTransaction(): coinbase output description has invalid encCiphertext"),
REJECT_INVALID,
"bad-cb-output-desc-invalid-encct");
}
}
}
}
uint256 dataToBeSigned;
if (!tx.vJoinSplit.empty() ||
@ -1219,13 +1268,11 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio
return state.DoS(100, error("CheckTransaction(): coinbase has joinsplits"),
REJECT_INVALID, "bad-cb-has-joinsplits");
// A coinbase transaction cannot have spend descriptions or output descriptions
// A coinbase transaction cannot have spend descriptions
if (tx.vShieldedSpend.size() > 0)
return state.DoS(100, error("CheckTransaction(): coinbase has spend descriptions"),
REJECT_INVALID, "bad-cb-has-spend-description");
if (tx.vShieldedOutput.size() > 0)
return state.DoS(100, error("CheckTransaction(): coinbase has output descriptions"),
REJECT_INVALID, "bad-cb-has-output-description");
// See ContextualCheckTransaction for consensus rules on coinbase output descriptions.
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
return state.DoS(100, error("CheckTransaction(): coinbase script size"),
@ -5305,8 +5352,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
// Reject incoming connections from nodes that don't know about the current epoch
const Consensus::Params& consensusParams = chainparams.GetConsensus();
auto currentEpoch = CurrentEpoch(GetHeight(), consensusParams);
if (pfrom->nVersion < consensusParams.vUpgrades[currentEpoch].nProtocolVersion)
{
if (pfrom->nVersion < consensusParams.vUpgrades[currentEpoch].nProtocolVersion &&
!(
chainparams.NetworkIDString() == "regtest" &&
!GetBoolArg("-nurejectoldversions", DEFAULT_NU_REJECT_OLD_VERSIONS)
)
) {
LogPrintf("peer=%d using obsolete version %i; disconnecting\n", pfrom->id, pfrom->nVersion);
pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE,
strprintf("Version must be %d or greater",
@ -5438,8 +5489,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
// 1. The version message has been received
// 2. Peer version is below the minimum version for the current epoch
else if (pfrom->nVersion < chainparams.GetConsensus().vUpgrades[
CurrentEpoch(GetHeight(), chainparams.GetConsensus())].nProtocolVersion)
{
CurrentEpoch(GetHeight(), chainparams.GetConsensus())].nProtocolVersion &&
!(
chainparams.NetworkIDString() == "regtest" &&
!GetBoolArg("-nurejectoldversions", DEFAULT_NU_REJECT_OLD_VERSIONS)
)
) {
LogPrintf("peer=%d using obsolete version %i; disconnecting\n", pfrom->id, pfrom->nVersion);
pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE,
strprintf("Version must be %d or greater",

View File

@ -111,6 +111,9 @@ static const bool DEFAULT_CHECKPOINTS_ENABLED = true;
static const bool DEFAULT_TXINDEX = false;
static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100;
/** Default for -nurejectoldversions */
static const bool DEFAULT_NU_REJECT_OLD_VERSIONS = true;
#define equihash_parameters_acceptable(N, K) \
((CBlockHeader::HEADER_SIZE + equihash_solution_size(N, K))*MAX_HEADERS_RESULTS < \
MAX_PROTOCOL_MESSAGE_LENGTH-1000)

View File

@ -26,11 +26,13 @@
#include "primitives/transaction.h"
#include "random.h"
#include "timedata.h"
#include "transaction_builder.h"
#include "ui_interface.h"
#include "util.h"
#include "utilmoneystr.h"
#include "validationinterface.h"
#include <librustzcash.h>
#include "sodium.h"
#include <boost/thread.hpp>
@ -113,7 +115,94 @@ void UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams,
}
}
CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& scriptPubKeyIn)
bool IsValidMinerAddress(const MinerAddress& minerAddr) {
return minerAddr.which() != 0;
}
class AddOutputsToCoinbaseTxAndSign : public boost::static_visitor<>
{
private:
CMutableTransaction &mtx;
const CChainParams &chainparams;
const int nHeight;
const CAmount nFees;
public:
AddOutputsToCoinbaseTxAndSign(
CMutableTransaction &mtx,
const CChainParams &chainparams,
const int nHeight,
const CAmount nFees) : mtx(mtx), chainparams(chainparams), nHeight(nHeight), nFees(nFees) {}
CAmount SetFoundersRewardAndGetMinerValue() const {
auto value = GetBlockSubsidy(nHeight, chainparams.GetConsensus());
if ((nHeight > 0) && (nHeight <= chainparams.GetConsensus().GetLastFoundersRewardBlockHeight(nHeight))) {
// Founders reward is 20% of the block subsidy
auto vFoundersReward = value / 5;
// Take some reward away from us
value -= vFoundersReward;
// And give it to the founders
mtx.vout.push_back(CTxOut(vFoundersReward, chainparams.GetFoundersRewardScriptAtHeight(nHeight)));
}
return value + nFees;
}
void operator()(const InvalidMinerAddress &invalid) const {}
// Create shielded output
void operator()(const libzcash::SaplingPaymentAddress &pa) const {
auto value = SetFoundersRewardAndGetMinerValue();
mtx.valueBalance = -value;
uint256 ovk;
auto note = libzcash::SaplingNote(pa, value);
auto output = OutputDescriptionInfo(ovk, note, {{0xF6}});
auto ctx = librustzcash_sapling_proving_ctx_init();
auto odesc = output.Build(ctx);
if (!odesc) {
librustzcash_sapling_proving_ctx_free(ctx);
throw new std::runtime_error("Failed to create shielded output for miner");
}
mtx.vShieldedOutput.push_back(odesc.get());
// Empty output script.
uint256 dataToBeSigned;
CScript scriptCode;
try {
dataToBeSigned = SignatureHash(
scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0,
CurrentEpochBranchId(nHeight, chainparams.GetConsensus()));
} catch (std::logic_error ex) {
librustzcash_sapling_proving_ctx_free(ctx);
throw ex;
}
librustzcash_sapling_binding_sig(
ctx,
mtx.valueBalance,
dataToBeSigned.begin(),
mtx.bindingSig.data());
librustzcash_sapling_proving_ctx_free(ctx);
}
// Create transparent output
void operator()(const boost::shared_ptr<CReserveScript> &coinbaseScript) const {
// Create the miner's output.
mtx.vout.resize(1);
// Add the FR output and fetch the miner's output value.
auto value = SetFoundersRewardAndGetMinerValue();
// Now fill in the miner's output.
mtx.vout[0].nValue = value;
mtx.vout[0].scriptPubKey = coinbaseScript->reserveScript;
}
};
CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const MinerAddress& minerAddress)
{
// Create new block
std::unique_ptr<CBlockTemplate> pblocktemplate(new CBlockTemplate());
@ -361,10 +450,6 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s
UpdateCoins(tx, view, nHeight);
BOOST_FOREACH(const OutputDescription &outDescription, tx.vShieldedOutput) {
sapling_tree.append(outDescription.cmu);
}
// Added
pblock->vtx.push_back(tx);
pblocktemplate->vTxFees.push_back(nTxFees);
@ -406,29 +491,26 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s
CMutableTransaction txNew = CreateNewContextualCMutableTransaction(chainparams.GetConsensus(), nHeight);
txNew.vin.resize(1);
txNew.vin[0].prevout.SetNull();
txNew.vout.resize(1);
txNew.vout[0].scriptPubKey = scriptPubKeyIn;
txNew.vout[0].nValue = GetBlockSubsidy(nHeight, chainparams.GetConsensus());
// Set to 0 so expiry height does not apply to coinbase txs
txNew.nExpiryHeight = 0;
if ((nHeight > 0) && (nHeight <= chainparams.GetConsensus().GetLastFoundersRewardBlockHeight(nHeight))) {
// Founders reward is 20% of the block subsidy
auto vFoundersReward = txNew.vout[0].nValue / 5;
// Take some reward away from us
txNew.vout[0].nValue -= vFoundersReward;
// Add outputs and sign
boost::apply_visitor(
AddOutputsToCoinbaseTxAndSign(txNew, chainparams, nHeight, nFees),
minerAddress);
// And give it to the founders
txNew.vout.push_back(CTxOut(vFoundersReward, chainparams.GetFoundersRewardScriptAtHeight(nHeight)));
}
// Add fees
txNew.vout[0].nValue += nFees;
txNew.vin[0].scriptSig = CScript() << nHeight << OP_0;
pblock->vtx[0] = txNew;
pblocktemplate->vTxFees[0] = -nFees;
// Update the Sapling commitment tree.
for (const CTransaction& tx : pblock->vtx) {
for (const OutputDescription& odesc : tx.vShieldedOutput) {
sapling_tree.append(odesc.cmu);
}
}
// Randomise nonce
arith_uint256 nonce = UintToArith256(GetRandHash());
// Clear the top and bottom 16 bits (for local use as thread flags and counters)
@ -469,18 +551,26 @@ class MinerAddressScript : public CReserveScript
void KeepScript() {}
};
void GetScriptForMinerAddress(boost::shared_ptr<CReserveScript> &script)
void GetMinerAddress(MinerAddress &minerAddress)
{
CTxDestination addr = DecodeDestination(GetArg("-mineraddress", ""));
if (!IsValidDestination(addr)) {
return;
// Try a transparent address first
auto mAddrArg = GetArg("-mineraddress", "");
CTxDestination addr = DecodeDestination(mAddrArg);
if (IsValidDestination(addr)) {
boost::shared_ptr<MinerAddressScript> mAddr(new MinerAddressScript());
CKeyID keyID = boost::get<CKeyID>(addr);
mAddr->reserveScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
minerAddress = mAddr;
} else {
// Try a Sapling address
auto zaddr = DecodePaymentAddress(mAddrArg);
if (IsValidPaymentAddress(zaddr)) {
if (boost::get<libzcash::SaplingPaymentAddress>(&zaddr) != nullptr) {
minerAddress = boost::get<libzcash::SaplingPaymentAddress>(zaddr);
}
}
}
boost::shared_ptr<MinerAddressScript> mAddr(new MinerAddressScript());
CKeyID keyID = boost::get<CKeyID>(addr);
script = mAddr;
script->reserveScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
}
void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
@ -536,8 +626,8 @@ void static BitcoinMiner(const CChainParams& chainparams)
// Each thread has its own counter
unsigned int nExtraNonce = 0;
boost::shared_ptr<CReserveScript> coinbaseScript;
GetMainSignals().ScriptForMining(coinbaseScript);
MinerAddress minerAddress;
GetMainSignals().AddressForMining(minerAddress);
unsigned int n = chainparams.GetConsensus().nEquihashN;
unsigned int k = chainparams.GetConsensus().nEquihashK;
@ -557,9 +647,10 @@ void static BitcoinMiner(const CChainParams& chainparams)
miningTimer.start();
try {
//throw an error if no script was provided
if (!coinbaseScript->reserveScript.size())
throw std::runtime_error("No coinbase script available (mining requires a wallet or -mineraddress)");
// Throw an error if no address valid for mining was provided.
if (!IsValidMinerAddress(minerAddress)) {
throw std::runtime_error("No miner address available (mining requires a wallet or -mineraddress)");
}
while (true) {
if (chainparams.MiningRequiresPeers()) {
@ -585,7 +676,7 @@ void static BitcoinMiner(const CChainParams& chainparams)
unsigned int nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
CBlockIndex* pindexPrev = chainActive.Tip();
unique_ptr<CBlockTemplate> pblocktemplate(CreateNewBlock(chainparams, coinbaseScript->reserveScript));
unique_ptr<CBlockTemplate> pblocktemplate(CreateNewBlock(chainparams, minerAddress));
if (!pblocktemplate.get())
{
if (GetArg("-mineraddress", "").empty()) {
@ -633,7 +724,7 @@ void static BitcoinMiner(const CChainParams& chainparams)
solver, pblock->nNonce.ToString());
std::function<bool(std::vector<unsigned char>)> validBlock =
[&pblock, &hashTarget, &chainparams, &m_cs, &cancelSolver, &coinbaseScript]
[&pblock, &hashTarget, &chainparams, &m_cs, &cancelSolver, &minerAddress]
(std::vector<unsigned char> soln) {
// Write the solution to the hash and compute the result.
LogPrint("pow", "- Checking solution against target\n");
@ -654,7 +745,7 @@ void static BitcoinMiner(const CChainParams& chainparams)
cancelSolver = false;
}
SetThreadPriority(THREAD_PRIORITY_LOWEST);
coinbaseScript->KeepScript();
boost::apply_visitor(KeepMinerAddress(), minerAddress);
// In regression test mode, stop mining after a block is found.
if (chainparams.MineBlocksOnDemand()) {

View File

@ -21,6 +21,28 @@ static const int DEFAULT_GENERATE_THREADS = 1;
static const bool DEFAULT_PRINTPRIORITY = false;
class InvalidMinerAddress {
public:
friend bool operator==(const InvalidMinerAddress &a, const InvalidMinerAddress &b) { return true; }
friend bool operator<(const InvalidMinerAddress &a, const InvalidMinerAddress &b) { return true; }
};
typedef boost::variant<InvalidMinerAddress, libzcash::SaplingPaymentAddress, boost::shared_ptr<CReserveScript>> MinerAddress;
class KeepMinerAddress : public boost::static_visitor<>
{
public:
KeepMinerAddress() {}
void operator()(const InvalidMinerAddress &invalid) const {}
void operator()(const libzcash::SaplingPaymentAddress &pa) const {}
void operator()(const boost::shared_ptr<CReserveScript> &coinbaseScript) const {
coinbaseScript->KeepScript();
}
};
bool IsValidMinerAddress(const MinerAddress& minerAddr);
struct CBlockTemplate
{
CBlock block;
@ -29,11 +51,11 @@ struct CBlockTemplate
};
/** Generate a new block, without valid proof-of-work */
CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& scriptPubKeyIn);
CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const MinerAddress& minerAddress);
#ifdef ENABLE_MINING
/** Get script for -mineraddress */
void GetScriptForMinerAddress(boost::shared_ptr<CReserveScript> &script);
/** Get -mineraddress */
void GetMinerAddress(MinerAddress &minerAddress);
/** Modify the extranonce in a block */
void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce);
/** Run the miner threads */

View File

@ -832,7 +832,12 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {
{
// Find any nodes which don't support the protocol version for the next upgrade
for (const CNodeRef &node : vEvictionCandidates) {
if (node->nVersion < params.vUpgrades[idx].nProtocolVersion) {
if (node->nVersion < params.vUpgrades[idx].nProtocolVersion &&
!(
Params().NetworkIDString() == "regtest" &&
!GetBoolArg("-nurejectoldversions", DEFAULT_NU_REJECT_OLD_VERSIONS)
)
) {
vTmpEvictionCandidates.push_back(node);
}
}

View File

@ -183,12 +183,13 @@ UniValue generate(const UniValue& params, bool fHelp)
int nHeight = 0;
int nGenerate = params[0].get_int();
boost::shared_ptr<CReserveScript> coinbaseScript;
GetMainSignals().ScriptForMining(coinbaseScript);
MinerAddress minerAddress;
GetMainSignals().AddressForMining(minerAddress);
//throw an error if no script was provided
if (!coinbaseScript->reserveScript.size())
throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available (mining requires a wallet or -mineraddress)");
// Throw an error if no address valid for mining was provided.
if (!IsValidMinerAddress(minerAddress)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "No miner address available (mining requires a wallet or -mineraddress)");
}
{ // Don't keep cs_main locked
LOCK(cs_main);
@ -202,7 +203,7 @@ UniValue generate(const UniValue& params, bool fHelp)
unsigned int k = Params().GetConsensus().nEquihashK;
while (nHeight < nHeightEnd)
{
std::unique_ptr<CBlockTemplate> pblocktemplate(CreateNewBlock(Params(), coinbaseScript->reserveScript));
std::unique_ptr<CBlockTemplate> pblocktemplate(CreateNewBlock(Params(), minerAddress));
if (!pblocktemplate.get())
throw JSONRPCError(RPC_INTERNAL_ERROR, "Couldn't create new block");
CBlock *pblock = &pblocktemplate->block;
@ -255,8 +256,8 @@ endloop:
++nHeight;
blockHashes.push_back(pblock->GetHash().GetHex());
//mark script as important because it was used at least for one coinbase output
coinbaseScript->KeepScript();
//mark miner address as important because it was used at least for one coinbase output
boost::apply_visitor(KeepMinerAddress(), minerAddress);
}
return blockHashes;
}
@ -610,19 +611,20 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
pblocktemplate = NULL;
}
boost::shared_ptr<CReserveScript> coinbaseScript;
GetMainSignals().ScriptForMining(coinbaseScript);
MinerAddress minerAddress;
GetMainSignals().AddressForMining(minerAddress);
// Throw an error if no script was provided
if (!coinbaseScript->reserveScript.size())
throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available (mining requires a wallet or -mineraddress)");
// Throw an error if no address valid for mining was provided.
if (!IsValidMinerAddress(minerAddress)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "No miner address available (mining requires a wallet or -mineraddress)");
}
pblocktemplate = CreateNewBlock(Params(), coinbaseScript->reserveScript);
pblocktemplate = CreateNewBlock(Params(), minerAddress);
if (!pblocktemplate)
throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory");
// Mark script as important because it was used at least for one coinbase output
coinbaseScript->KeepScript();
boost::apply_visitor(KeepMinerAddress(), minerAddress);
// Need to update only after we know CreateNewBlock succeeded
pindexPrev = pindexPrevNew;

View File

@ -135,11 +135,23 @@ struct {
{"00000000000000000000000000000000000000000000000000000000000022de", "007f388bed6b91756ea3e0866716ef6e9485fae6160195c7cda5c1e43f96ee359e105bcf4e8c293690420939124f04a0196363910421187811575929db40500b0bfdd1e8964aa334b801e3339a336d585a30852f1dc294a2d3d36f9ecc747458f3d41b4572415496df2a9fb1f882156cdabf9f65e681f38019865d6d47482277e24c9b8973eb34a41254faae4c5e2caa9dde5925ec118f3d8fa767ae00f434645957154367afe72000c59c79182c8faddd24424b9ebbb09ccd651b00540c96b9c7eec648a28a1d72c2e575d0f2250078511a011598db8e0788edf0ddc15ae24b62f63d6f93f71a2743a3c43ece55471a9802a76f31561a6f365c3647029bfa736395883afc0632bc25d4a8661b25d5aa0310f3c3fd3a183e75d359d6de3e5910b5dfbb74b7660af906917dc42b12e3e484aae1dbd20eaee037ef301572b7fc24d85b4aff9c82b27dcd421cee1639230d0188fec59f0dd4c0ced69c1ad07abd23692b1bd30735af942df597dcf6f403a36371bc416cf3e29a58570f586b05c357dc49515689788ad9581b8887dd913a41dc35e1ac9c9f9f4ea534eb6b36cc8af0299b6d3905750425da0366bdc59a7824477d7946b6f35c4ec90b8e61790fa74a4fa92396ea856661027828d40abb11dbe36bba516fe8ec8913106677285a4790d8034d1d1bf9fd87990889ddffc369b954a3d1c172be7e1812226c2b100cbe82c42bf4423456b6cb2bac3b4828135cb54f7a933a01f7f4a2057ad92136ba8e19fec313b412d43c089a71f06fd1625329b78d49ac92c59e4080932ddb1645910fd874dfb1f358e214231f62041acc41fd2c4e7b7127b3042459e1457f6b307fce9825aa4d2b942277f52665f2a77dd107b4f16cb3280f20c7551ff6cd855f97a6144131f69bab5648fb4b81261eefbf629094e8bcc4e36077f46d51a647da51fc01dca9a9ac12e2f7e2e2b1c9229dae099e95370177143d3b38ab661f19758494a01b32f0c27155b45a872a867dc50f9d76473695e9e2c4f9357f5ba6bb6c455d985f4e2c21486fde6576c6a8ceda6e010a7dc2b504130f429ac33376781ee4af5bbe8d768005bc4cb5092b15c4f296a8bd8c54a298eecd790a5161755a8605cc46bf890b8ff93d508501842b78c7261e5deeb1096891c528a300e57bf2f0aa9e8af2623cdf16bba20427704120484b6af8be26e4983d2685c783ce85d0174f84598719c6beefcc3603a94d4aa62750725df50671d7f9903ec255f779643ebd2fd8122fae3319e61928dcdaa44880d6a483140de63d2d7d7dc9dd449e0ee00d908e0f2164fc054198641e8fb0d74279c9b4117884b9335028a9f50c7223d3c03675ecf73329e52603f77f20cffba99356e51a365b75825f7db56d77542784f3c2663c493a2e564d73f753e9d6ebb0c2f2027a2330a7117c67a20507474fc47282a02cb572de17bbc7a335959316f74a05e3687cfb5227bc5b1b7f084f50902760e77740d420df9a495521c09b911e5f199a8343918b8386fc74f22552a76524a22c8c70ff06084e7fefe9b3ab98e004fadf35eb5f60483f287851712d90ebdd6b512877170d3b7fb34f16813917ae3b5ed54ede6081bdd7cc646fb336658121fd8fbafc52959b48d13375dfa4ce8616c157533a05ee1dc1120f215c348b54357d68adb4da7f5f48d55c005b3e7a23d05746e44d968f7601d4dbdff702861030d6e3a4140e6e1a29978be541f713f8e2cc9aa1ac32fecc941ee4aa4c41bc7f91ea5328ff87cbf35a8de17d1d3d1ad6d4384b0df52d10b3984d62e1678e86dd150ee425490bc727ee7107fda0f5d2433ab1c5d407be9d123fad5c201355601d926d3923787be86a4aa5be0b8d5750171ad658f8e97798b5dcaed46345a9af70c441"},
};
// Copied from src/miner.cpp
class MinerAddressScript : public CReserveScript
{
// CReserveScript requires implementing this function, so that if an
// internal (not-visible) wallet address is used, the wallet can mark it as
// important when a block is mined (so it then appears to the user).
// If -mineraddress is set, the user already knows about and is managing the
// address, so we don't need to do anything here.
void KeepScript() {}
};
// NOTE: These tests rely on CreateNewBlock doing its own self-validation!
BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
{
const CChainParams& chainparams = Params(CBaseChainParams::MAIN);
CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
boost::shared_ptr<MinerAddressScript> scriptPubKey(new MinerAddressScript());
scriptPubKey->reserveScript = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
CBlockTemplate *pblocktemplate;
CMutableTransaction tx,tx2;
CScript script;

View File

@ -153,7 +153,10 @@ TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>&
unsigned int n = chainparams.GetConsensus().nEquihashN;
unsigned int k = chainparams.GetConsensus().nEquihashK;
CBlockTemplate *pblocktemplate = CreateNewBlock(chainparams, scriptPubKey);
boost::shared_ptr<CReserveScript> mAddr(new CReserveScript());
mAddr->reserveScript = scriptPubKey;
CBlockTemplate *pblocktemplate = CreateNewBlock(chainparams, mAddr);
CBlock& block = pblocktemplate->block;
// Replace mempool-selected txns with just coinbase plus passed-in txns:

View File

@ -451,11 +451,6 @@ void test_simple_sapling_invalidity(uint32_t consensusBranchId, CMutableTransact
vout.nValue = 1;
newTx.vout.push_back(vout);
newTx.vShieldedOutput.push_back(OutputDescription());
BOOST_CHECK(!CheckTransactionWithoutProofVerification(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-cb-has-output-description");
newTx.vShieldedSpend.push_back(SpendDescription());
BOOST_CHECK(!CheckTransactionWithoutProofVerification(newTx, state));

View File

@ -22,6 +22,52 @@ SpendDescriptionInfo::SpendDescriptionInfo(
librustzcash_sapling_generate_r(alpha.begin());
}
boost::optional<OutputDescription> OutputDescriptionInfo::Build(void* ctx) {
auto cmu = this->note.cmu();
if (!cmu) {
return boost::none;
}
libzcash::SaplingNotePlaintext notePlaintext(this->note, this->memo);
auto res = notePlaintext.encrypt(this->note.pk_d);
if (!res) {
return boost::none;
}
auto enc = res.get();
auto encryptor = enc.second;
libzcash::SaplingPaymentAddress address(this->note.d, this->note.pk_d);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << address;
std::vector<unsigned char> addressBytes(ss.begin(), ss.end());
OutputDescription odesc;
if (!librustzcash_sapling_output_proof(
ctx,
encryptor.get_esk().begin(),
addressBytes.data(),
this->note.r.begin(),
this->note.value(),
odesc.cv.begin(),
odesc.zkproof.begin())) {
return boost::none;
}
odesc.cmu = *cmu;
odesc.ephemeralKey = encryptor.get_epk();
odesc.encCiphertext = enc.first;
libzcash::SaplingOutgoingPlaintext outPlaintext(this->note.pk_d, encryptor.get_esk());
odesc.outCiphertext = outPlaintext.encrypt(
this->ovk,
odesc.cv,
odesc.cmu,
encryptor);
return odesc;
}
TransactionBuilderResult::TransactionBuilderResult(const CTransaction& tx) : maybeTx(tx) {}
TransactionBuilderResult::TransactionBuilderResult(const std::string& error) : maybeError(error) {}
@ -302,51 +348,19 @@ TransactionBuilderResult TransactionBuilder::Build()
// Create Sapling OutputDescriptions
for (auto output : outputs) {
auto cmu = output.note.cmu();
if (!cmu) {
// Check this out here as well to provide better logging.
if (!output.note.cmu()) {
librustzcash_sapling_proving_ctx_free(ctx);
return TransactionBuilderResult("Output is invalid");
}
libzcash::SaplingNotePlaintext notePlaintext(output.note, output.memo);
auto res = notePlaintext.encrypt(output.note.pk_d);
if (!res) {
auto odesc = output.Build(ctx);
if (!odesc) {
librustzcash_sapling_proving_ctx_free(ctx);
return TransactionBuilderResult("Failed to encrypt note");
}
auto enc = res.get();
auto encryptor = enc.second;
libzcash::SaplingPaymentAddress address(output.note.d, output.note.pk_d);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << address;
std::vector<unsigned char> addressBytes(ss.begin(), ss.end());
OutputDescription odesc;
if (!librustzcash_sapling_output_proof(
ctx,
encryptor.get_esk().begin(),
addressBytes.data(),
output.note.r.begin(),
output.note.value(),
odesc.cv.begin(),
odesc.zkproof.begin())) {
librustzcash_sapling_proving_ctx_free(ctx);
return TransactionBuilderResult("Output proof failed");
return TransactionBuilderResult("Failed to create output description");
}
odesc.cmu = *cmu;
odesc.ephemeralKey = encryptor.get_epk();
odesc.encCiphertext = enc.first;
libzcash::SaplingOutgoingPlaintext outPlaintext(output.note.pk_d, encryptor.get_esk());
odesc.outCiphertext = outPlaintext.encrypt(
output.ovk,
odesc.cv,
odesc.cmu,
encryptor);
mtx.vShieldedOutput.push_back(odesc);
mtx.vShieldedOutput.push_back(odesc.get());
}
//

View File

@ -43,6 +43,8 @@ struct OutputDescriptionInfo {
uint256 ovk,
libzcash::SaplingNote note,
std::array<unsigned char, ZC_MEMO_SIZE> memo) : ovk(ovk), note(note), memo(memo) {}
boost::optional<OutputDescription> Build(void* ctx);
};
struct TransparentInputInfo {

View File

@ -230,6 +230,27 @@ void RegtestDeactivateBlossom() {
SelectParams(CBaseChainParams::MAIN);
}
const Consensus::Params& RegtestActivateHeartwood(bool updatePow, int heartwoodActivationHeight) {
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_BLOSSOM, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_HEARTWOOD, heartwoodActivationHeight);
if (updatePow) {
UpdateRegtestPow(32, 16, uint256S("0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"));
}
return Params().GetConsensus();
}
void RegtestDeactivateHeartwood() {
UpdateRegtestPow(0, 0, uint256S("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"));
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_HEARTWOOD, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_BLOSSOM, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
SelectParams(CBaseChainParams::MAIN);
}
libzcash::SaplingExtendedSpendingKey GetTestMasterSaplingSpendingKey() {
std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);

View File

@ -49,6 +49,10 @@ const Consensus::Params& RegtestActivateBlossom(bool updatePow, int blossomActiv
void RegtestDeactivateBlossom();
const Consensus::Params& RegtestActivateHeartwood(bool updatePow, int heartwoodActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
void RegtestDeactivateHeartwood();
libzcash::SaplingExtendedSpendingKey GetTestMasterSaplingSpendingKey();
CKey AddTestCKeyToKeyStore(CBasicKeyStore& keyStore);

View File

@ -33,13 +33,13 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
g_signals.Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1));
g_signals.BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2));
g_signals.ScriptForMining.connect(boost::bind(&CValidationInterface::GetScriptForMining, pwalletIn, _1));
g_signals.AddressForMining.connect(boost::bind(&CValidationInterface::GetAddressForMining, pwalletIn, _1));
g_signals.BlockFound.connect(boost::bind(&CValidationInterface::ResetRequestCount, pwalletIn, _1));
}
void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.BlockFound.disconnect(boost::bind(&CValidationInterface::ResetRequestCount, pwalletIn, _1));
g_signals.ScriptForMining.disconnect(boost::bind(&CValidationInterface::GetScriptForMining, pwalletIn, _1));
g_signals.AddressForMining.disconnect(boost::bind(&CValidationInterface::GetAddressForMining, pwalletIn, _1));
g_signals.BlockChecked.disconnect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2));
g_signals.Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1));
g_signals.Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
@ -53,7 +53,7 @@ void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
void UnregisterAllValidationInterfaces() {
g_signals.BlockFound.disconnect_all_slots();
g_signals.ScriptForMining.disconnect_all_slots();
g_signals.AddressForMining.disconnect_all_slots();
g_signals.BlockChecked.disconnect_all_slots();
g_signals.Broadcast.disconnect_all_slots();
g_signals.Inventory.disconnect_all_slots();

View File

@ -9,6 +9,7 @@
#include <boost/signals2/signal.hpp>
#include <boost/shared_ptr.hpp>
#include "miner.h"
#include "zcash/IncrementalMerkleTree.hpp"
class CBlock;
@ -40,7 +41,7 @@ protected:
virtual void Inventory(const uint256 &hash) {}
virtual void ResendWalletTransactions(int64_t nBestBlockTime) {}
virtual void BlockChecked(const CBlock&, const CValidationState&) {}
virtual void GetScriptForMining(boost::shared_ptr<CReserveScript>&) {};
virtual void GetAddressForMining(MinerAddress&) {};
virtual void ResetRequestCount(const uint256 &hash) {};
friend void ::RegisterValidationInterface(CValidationInterface*);
friend void ::UnregisterValidationInterface(CValidationInterface*);
@ -66,8 +67,8 @@ struct CMainSignals {
boost::signals2::signal<void (int64_t nBestBlockTime)> Broadcast;
/** Notifies listeners of a block validation result */
boost::signals2::signal<void (const CBlock&, const CValidationState&)> BlockChecked;
/** Notifies listeners that a key for mining is required (coinbase) */
boost::signals2::signal<void (boost::shared_ptr<CReserveScript>&)> ScriptForMining;
/** Notifies listeners that an address for mining is required (coinbase) */
boost::signals2::signal<void (MinerAddress&)> AddressForMining;
/** Notifies listeners that a block has been successfully mined */
boost::signals2::signal<void (const uint256 &)> BlockFound;
};

View File

@ -4245,7 +4245,7 @@ void CWallet::UpdatedTransaction(const uint256 &hashTx)
}
}
void CWallet::GetScriptForMining(boost::shared_ptr<CReserveScript> &script)
void CWallet::GetAddressForMining(MinerAddress &minerAddress)
{
if (!GetArg("-mineraddress", "").empty()) {
return;
@ -4256,8 +4256,8 @@ void CWallet::GetScriptForMining(boost::shared_ptr<CReserveScript> &script)
if (!rKey->GetReservedKey(pubkey))
return;
script = rKey;
script->reserveScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(pubkey.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG;
rKey->reserveScript = CScript() << OP_DUP << OP_HASH160 << ToByteVector(pubkey.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG;
minerAddress = rKey;
}
void CWallet::LockCoin(COutPoint& output)
@ -4885,12 +4885,16 @@ void CWallet::GetFilteredNotes(
// Filter the transactions before checking for notes
if (!CheckFinalTx(wtx) ||
wtx.GetBlocksToMaturity() > 0 ||
wtx.GetDepthInMainChain() < minDepth ||
wtx.GetDepthInMainChain() > maxDepth) {
continue;
}
// Filter coinbase transactions that don't have Sapling outputs
if (wtx.IsCoinBase() && wtx.mapSaplingNoteData.empty()) {
continue;
}
for (auto & pair : wtx.mapSproutNoteData) {
JSOutPoint jsop = pair.first;
SproutNoteData nd = pair.second;

View File

@ -1268,7 +1268,7 @@ public:
}
}
void GetScriptForMining(boost::shared_ptr<CReserveScript> &script);
void GetAddressForMining(MinerAddress &minerAddress);
void ResetRequestCount(const uint256 &hash)
{
LOCK(cs_wallet);