Select Orchard receivers from UA miner addresses once NU5 activates

Closes zcash/zcash#5023.
This commit is contained in:
Jack Grigg 2022-02-26 01:21:05 +00:00
parent 1c31a1c7d8
commit 3fa58149b0
4 changed files with 135 additions and 19 deletions

View File

@ -6,10 +6,12 @@
from decimal import Decimal
from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework
from test_framework.mininode import nuparams
from test_framework.mininode import COIN, nuparams
from test_framework.util import (
BLOSSOM_BRANCH_ID,
HEARTWOOD_BRANCH_ID,
CANOPY_BRANCH_ID,
NU5_BRANCH_ID,
assert_equal,
assert_raises,
bitcoind_processes,
@ -28,8 +30,12 @@ class ShieldCoinbaseTest (BitcoinTestFramework):
def start_node_with(self, index, extra_args=[]):
args = [
'-experimentalfeatures',
'-orchardwallet',
nuparams(BLOSSOM_BRANCH_ID, 1),
nuparams(HEARTWOOD_BRANCH_ID, 10),
nuparams(CANOPY_BRANCH_ID, 20),
nuparams(NU5_BRANCH_ID, 20),
"-nurejectoldversions=false",
]
return start_node(index, self.options.tmpdir, args + extra_args)
@ -117,5 +123,50 @@ class ShieldCoinbaseTest (BitcoinTestFramework):
assert_equal(self.nodes[0].z_getbalance(node0_taddr), 2)
assert_equal(self.nodes[1].z_getbalance(node1_zaddr), 1)
# Generate a Unified Address for node 1
self.nodes[1].z_getnewaccount()
node1_addr0 = self.nodes[1].z_getaddressforaccount(0)
assert_equal(node1_addr0['account'], 0)
assert_equal(set(node1_addr0['pools']), set(['transparent', 'sapling', 'orchard']))
node1_ua = node1_addr0['unifiedaddress']
# Set node 1's miner address to the UA
self.nodes[1].stop()
bitcoind_processes[1].wait()
self.nodes[1] = self.start_node_with(1, [
"-mineraddress=%s" % node1_ua,
])
connect_nodes(self.nodes[1], 0)
# The UA starts with zero balance.
assert_equal(self.nodes[1].z_getbalanceforaccount(0)['pools'], {})
# Node 1 can mine blocks because the miner selects the Sapling receiver
# of its UA.
print("Mining block with node 1")
self.nodes[1].generate(1)
self.sync_all()
# The UA balance should show that Sapling funds were received.
assert_equal(self.nodes[1].z_getbalanceforaccount(0)['pools'], {
'sapling': {'valueZat': 5 * COIN },
})
# Activate NU5
print("Activating NU5")
self.nodes[0].generate(7)
self.sync_all()
# Now any block mined by node 1 should use the Orchard receiver of its UA.
print("Mining block with node 1")
self.nodes[1].generate(1)
self.sync_all()
assert_equal(self.nodes[1].z_getbalanceforaccount(0)['pools'], {
'sapling': {'valueZat': 5 * COIN },
# 6.25 ZEC because the FR always ends when Canopy activates, and
# regtest has no defined funding streams.
'orchard': {'valueZat': 6.25 * COIN },
})
if __name__ == '__main__':
ShieldCoinbaseTest().main()

View File

@ -1105,7 +1105,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
#ifdef ENABLE_MINING
if (mapArgs.count("-mineraddress")) {
auto addr = keyIO.DecodePaymentAddress(mapArgs["-mineraddress"]);
if (!(addr.has_value() && std::visit(ExtractMinerAddress(), addr.value()).has_value())) {
if (!(addr.has_value() && std::visit(ExtractMinerAddress(chainparams.GetConsensus(), 0), addr.value()).has_value())) {
return InitError(strprintf(
_("Invalid address for -mineraddress=<addr>: '%s' (must be a Sapling or transparent P2PKH address)"),
mapArgs["-mineraddress"]));

View File

@ -214,31 +214,72 @@ public:
return miner_reward + nFees;
}
void ComputeBindingSig(void* ctx) const {
void ComputeBindingSig(void* saplingCtx, std::optional<orchard::UnauthorizedBundle> orchardBundle) const {
// Empty output script.
uint256 dataToBeSigned;
CScript scriptCode;
try {
dataToBeSigned = SignatureHash(
scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0,
CurrentEpochBranchId(nHeight, chainparams.GetConsensus()));
if (orchardBundle.has_value()) {
// Orchard is only usable with v5+ transactions.
dataToBeSigned = ProduceZip244SignatureHash(mtx, orchardBundle.value());
} else {
CScript scriptCode;
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);
librustzcash_sapling_proving_ctx_free(saplingCtx);
throw ex;
}
if (orchardBundle.has_value()) {
auto authorizedBundle = orchardBundle.value().ProveAndSign(dataToBeSigned);
if (authorizedBundle.has_value()) {
mtx.orchardBundle = authorizedBundle.value();
} else {
librustzcash_sapling_proving_ctx_free(saplingCtx);
throw new std::runtime_error("Failed to create Orchard proof or signatures");
}
}
bool success = librustzcash_sapling_binding_sig(
ctx,
saplingCtx,
mtx.valueBalanceSapling,
dataToBeSigned.begin(),
mtx.bindingSig.data());
if (!success) {
librustzcash_sapling_proving_ctx_free(ctx);
librustzcash_sapling_proving_ctx_free(saplingCtx);
throw new std::runtime_error("An error occurred computing the binding signature.");
}
}
// Create Orchard output
void operator()(const libzcash::OrchardRawAddress &to) const {
auto ctx = librustzcash_sapling_proving_ctx_init();
// `enableSpends` must be set to `false` for coinbase transactions. This
// means the Orchard anchor is unconstrained, so we set it to the empty
// tree root via a null (all zeroes) uint256.
uint256 orchardAnchor;
auto builder = orchard::Builder(false, true, orchardAnchor);
// Shielded coinbase outputs must be recoverable with an all-zeroes ovk.
uint256 ovk;
auto miner_reward = SetFoundersRewardAndGetMinerValue(ctx);
builder.AddOutput(ovk, to, miner_reward, std::nullopt);
auto bundle = builder.Build();
if (!bundle.has_value()) {
librustzcash_sapling_proving_ctx_free(ctx);
throw new std::runtime_error("Failed to create shielded output for miner");
}
ComputeBindingSig(ctx, std::move(bundle));
librustzcash_sapling_proving_ctx_free(ctx);
}
// Create shielded output
void operator()(const libzcash::SaplingPaymentAddress &pa) const {
auto ctx = librustzcash_sapling_proving_ctx_init();
@ -258,7 +299,7 @@ public:
}
mtx.vShieldedOutput.push_back(odesc.value());
ComputeBindingSig(ctx);
ComputeBindingSig(ctx, std::nullopt);
librustzcash_sapling_proving_ctx_free(ctx);
}
@ -277,7 +318,7 @@ public:
mtx.vout[0] = CTxOut(value, coinbaseScript->reserveScript);
if (mtx.vShieldedOutput.size() > 0) {
ComputeBindingSig(ctx);
ComputeBindingSig(ctx, std::nullopt);
}
librustzcash_sapling_proving_ctx_free(ctx);
@ -717,12 +758,19 @@ std::optional<MinerAddress> ExtractMinerAddress::operator()(const libzcash::Sapl
return addr;
}
std::optional<MinerAddress> ExtractMinerAddress::operator()(const libzcash::UnifiedAddress &addr) const {
for (const auto& receiver: addr) {
if (std::holds_alternative<libzcash::SaplingPaymentAddress>(receiver)) {
return std::get<libzcash::SaplingPaymentAddress>(receiver);
}
auto preferred = addr.GetPreferredRecipientAddress(consensus, height);
if (preferred.has_value()) {
std::optional<MinerAddress> ret;
std::visit(match {
[&](const libzcash::OrchardRawAddress addr) { ret = MinerAddress(addr); },
[&](const libzcash::SaplingPaymentAddress addr) { ret = MinerAddress(addr); },
[&](const CKeyID keyID) { ret = operator()(keyID); },
[&](const auto other) { ret = std::nullopt; }
}, preferred.value());
return ret;
} else {
return std::nullopt;
}
return std::nullopt;
}
@ -730,10 +778,18 @@ void GetMinerAddress(std::optional<MinerAddress> &minerAddress)
{
KeyIO keyIO(Params());
// If the user sets a UA miner address with an Orchard component, we want to ensure we
// start using it once we reach that height.
int height;
{
LOCK(cs_main);
height = chainActive.Height() + 1;
}
auto mAddrArg = GetArg("-mineraddress", "");
auto zaddr0 = keyIO.DecodePaymentAddress(mAddrArg);
if (zaddr0.has_value()) {
auto zaddr = std::visit(ExtractMinerAddress(), zaddr0.value());
auto zaddr = std::visit(ExtractMinerAddress(Params().GetConsensus(), height), zaddr0.value());
if (zaddr.has_value()) {
minerAddress = zaddr.value();
}

View File

@ -24,13 +24,18 @@ static const int DEFAULT_GENERATE_THREADS = 1;
static const bool DEFAULT_PRINTPRIORITY = false;
typedef std::variant<
libzcash::OrchardRawAddress,
libzcash::SaplingPaymentAddress,
boost::shared_ptr<CReserveScript>> MinerAddress;
class ExtractMinerAddress
{
const Consensus::Params& consensus;
int height;
public:
ExtractMinerAddress() {}
ExtractMinerAddress(const Consensus::Params& consensus, int height) :
consensus(consensus), height(height) {}
std::optional<MinerAddress> operator()(const CKeyID &keyID) const;
std::optional<MinerAddress> operator()(const CScriptID &addr) const;
@ -44,6 +49,7 @@ class KeepMinerAddress
public:
KeepMinerAddress() {}
void operator()(const libzcash::OrchardRawAddress &addr) const {}
void operator()(const libzcash::SaplingPaymentAddress &pa) const {}
void operator()(const boost::shared_ptr<CReserveScript> &coinbaseScript) const {
coinbaseScript->KeepScript();
@ -57,6 +63,9 @@ class IsValidMinerAddress
public:
IsValidMinerAddress() {}
bool operator()(const libzcash::OrchardRawAddress &addr) const {
return true;
}
bool operator()(const libzcash::SaplingPaymentAddress &pa) const {
return true;
}