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 decimal import Decimal
|
||||||
from test_framework.authproxy import JSONRPCException
|
from test_framework.authproxy import JSONRPCException
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
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 (
|
from test_framework.util import (
|
||||||
BLOSSOM_BRANCH_ID,
|
BLOSSOM_BRANCH_ID,
|
||||||
HEARTWOOD_BRANCH_ID,
|
HEARTWOOD_BRANCH_ID,
|
||||||
|
CANOPY_BRANCH_ID,
|
||||||
|
NU5_BRANCH_ID,
|
||||||
assert_equal,
|
assert_equal,
|
||||||
assert_raises,
|
assert_raises,
|
||||||
bitcoind_processes,
|
bitcoind_processes,
|
||||||
|
@ -28,8 +30,12 @@ class ShieldCoinbaseTest (BitcoinTestFramework):
|
||||||
|
|
||||||
def start_node_with(self, index, extra_args=[]):
|
def start_node_with(self, index, extra_args=[]):
|
||||||
args = [
|
args = [
|
||||||
|
'-experimentalfeatures',
|
||||||
|
'-orchardwallet',
|
||||||
nuparams(BLOSSOM_BRANCH_ID, 1),
|
nuparams(BLOSSOM_BRANCH_ID, 1),
|
||||||
nuparams(HEARTWOOD_BRANCH_ID, 10),
|
nuparams(HEARTWOOD_BRANCH_ID, 10),
|
||||||
|
nuparams(CANOPY_BRANCH_ID, 20),
|
||||||
|
nuparams(NU5_BRANCH_ID, 20),
|
||||||
"-nurejectoldversions=false",
|
"-nurejectoldversions=false",
|
||||||
]
|
]
|
||||||
return start_node(index, self.options.tmpdir, args + extra_args)
|
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[0].z_getbalance(node0_taddr), 2)
|
||||||
assert_equal(self.nodes[1].z_getbalance(node1_zaddr), 1)
|
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__':
|
if __name__ == '__main__':
|
||||||
ShieldCoinbaseTest().main()
|
ShieldCoinbaseTest().main()
|
||||||
|
|
|
@ -1105,7 +1105,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||||
#ifdef ENABLE_MINING
|
#ifdef ENABLE_MINING
|
||||||
if (mapArgs.count("-mineraddress")) {
|
if (mapArgs.count("-mineraddress")) {
|
||||||
auto addr = keyIO.DecodePaymentAddress(mapArgs["-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(
|
return InitError(strprintf(
|
||||||
_("Invalid address for -mineraddress=<addr>: '%s' (must be a Sapling or transparent P2PKH address)"),
|
_("Invalid address for -mineraddress=<addr>: '%s' (must be a Sapling or transparent P2PKH address)"),
|
||||||
mapArgs["-mineraddress"]));
|
mapArgs["-mineraddress"]));
|
||||||
|
|
|
@ -214,31 +214,72 @@ public:
|
||||||
return miner_reward + nFees;
|
return miner_reward + nFees;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComputeBindingSig(void* ctx) const {
|
void ComputeBindingSig(void* saplingCtx, std::optional<orchard::UnauthorizedBundle> orchardBundle) const {
|
||||||
// Empty output script.
|
// Empty output script.
|
||||||
uint256 dataToBeSigned;
|
uint256 dataToBeSigned;
|
||||||
CScript scriptCode;
|
|
||||||
try {
|
try {
|
||||||
dataToBeSigned = SignatureHash(
|
if (orchardBundle.has_value()) {
|
||||||
scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0,
|
// Orchard is only usable with v5+ transactions.
|
||||||
CurrentEpochBranchId(nHeight, chainparams.GetConsensus()));
|
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) {
|
} catch (std::logic_error ex) {
|
||||||
librustzcash_sapling_proving_ctx_free(ctx);
|
librustzcash_sapling_proving_ctx_free(saplingCtx);
|
||||||
throw ex;
|
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(
|
bool success = librustzcash_sapling_binding_sig(
|
||||||
ctx,
|
saplingCtx,
|
||||||
mtx.valueBalanceSapling,
|
mtx.valueBalanceSapling,
|
||||||
dataToBeSigned.begin(),
|
dataToBeSigned.begin(),
|
||||||
mtx.bindingSig.data());
|
mtx.bindingSig.data());
|
||||||
|
|
||||||
if (!success) {
|
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.");
|
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
|
// Create shielded output
|
||||||
void operator()(const libzcash::SaplingPaymentAddress &pa) const {
|
void operator()(const libzcash::SaplingPaymentAddress &pa) const {
|
||||||
auto ctx = librustzcash_sapling_proving_ctx_init();
|
auto ctx = librustzcash_sapling_proving_ctx_init();
|
||||||
|
@ -258,7 +299,7 @@ public:
|
||||||
}
|
}
|
||||||
mtx.vShieldedOutput.push_back(odesc.value());
|
mtx.vShieldedOutput.push_back(odesc.value());
|
||||||
|
|
||||||
ComputeBindingSig(ctx);
|
ComputeBindingSig(ctx, std::nullopt);
|
||||||
|
|
||||||
librustzcash_sapling_proving_ctx_free(ctx);
|
librustzcash_sapling_proving_ctx_free(ctx);
|
||||||
}
|
}
|
||||||
|
@ -277,7 +318,7 @@ public:
|
||||||
mtx.vout[0] = CTxOut(value, coinbaseScript->reserveScript);
|
mtx.vout[0] = CTxOut(value, coinbaseScript->reserveScript);
|
||||||
|
|
||||||
if (mtx.vShieldedOutput.size() > 0) {
|
if (mtx.vShieldedOutput.size() > 0) {
|
||||||
ComputeBindingSig(ctx);
|
ComputeBindingSig(ctx, std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
librustzcash_sapling_proving_ctx_free(ctx);
|
librustzcash_sapling_proving_ctx_free(ctx);
|
||||||
|
@ -717,12 +758,19 @@ std::optional<MinerAddress> ExtractMinerAddress::operator()(const libzcash::Sapl
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
std::optional<MinerAddress> ExtractMinerAddress::operator()(const libzcash::UnifiedAddress &addr) const {
|
std::optional<MinerAddress> ExtractMinerAddress::operator()(const libzcash::UnifiedAddress &addr) const {
|
||||||
for (const auto& receiver: addr) {
|
auto preferred = addr.GetPreferredRecipientAddress(consensus, height);
|
||||||
if (std::holds_alternative<libzcash::SaplingPaymentAddress>(receiver)) {
|
if (preferred.has_value()) {
|
||||||
return std::get<libzcash::SaplingPaymentAddress>(receiver);
|
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());
|
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 mAddrArg = GetArg("-mineraddress", "");
|
||||||
auto zaddr0 = keyIO.DecodePaymentAddress(mAddrArg);
|
auto zaddr0 = keyIO.DecodePaymentAddress(mAddrArg);
|
||||||
if (zaddr0.has_value()) {
|
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()) {
|
if (zaddr.has_value()) {
|
||||||
minerAddress = zaddr.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;
|
static const bool DEFAULT_PRINTPRIORITY = false;
|
||||||
|
|
||||||
typedef std::variant<
|
typedef std::variant<
|
||||||
|
libzcash::OrchardRawAddress,
|
||||||
libzcash::SaplingPaymentAddress,
|
libzcash::SaplingPaymentAddress,
|
||||||
boost::shared_ptr<CReserveScript>> MinerAddress;
|
boost::shared_ptr<CReserveScript>> MinerAddress;
|
||||||
|
|
||||||
class ExtractMinerAddress
|
class ExtractMinerAddress
|
||||||
{
|
{
|
||||||
|
const Consensus::Params& consensus;
|
||||||
|
int height;
|
||||||
|
|
||||||
public:
|
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 CKeyID &keyID) const;
|
||||||
std::optional<MinerAddress> operator()(const CScriptID &addr) const;
|
std::optional<MinerAddress> operator()(const CScriptID &addr) const;
|
||||||
|
@ -44,6 +49,7 @@ class KeepMinerAddress
|
||||||
public:
|
public:
|
||||||
KeepMinerAddress() {}
|
KeepMinerAddress() {}
|
||||||
|
|
||||||
|
void operator()(const libzcash::OrchardRawAddress &addr) const {}
|
||||||
void operator()(const libzcash::SaplingPaymentAddress &pa) const {}
|
void operator()(const libzcash::SaplingPaymentAddress &pa) const {}
|
||||||
void operator()(const boost::shared_ptr<CReserveScript> &coinbaseScript) const {
|
void operator()(const boost::shared_ptr<CReserveScript> &coinbaseScript) const {
|
||||||
coinbaseScript->KeepScript();
|
coinbaseScript->KeepScript();
|
||||||
|
@ -57,6 +63,9 @@ class IsValidMinerAddress
|
||||||
public:
|
public:
|
||||||
IsValidMinerAddress() {}
|
IsValidMinerAddress() {}
|
||||||
|
|
||||||
|
bool operator()(const libzcash::OrchardRawAddress &addr) const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
bool operator()(const libzcash::SaplingPaymentAddress &pa) const {
|
bool operator()(const libzcash::SaplingPaymentAddress &pa) const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue