Select Orchard receivers from UA miner addresses once NU5 activates
Closes zcash/zcash#5023.
This commit is contained in:
parent
1c31a1c7d8
commit
3fa58149b0
|
@ -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()
|
||||
|
|
|
@ -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"]));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
11
src/miner.h
11
src/miner.h
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue