Add Orchard & unified address support to z_viewtransaction.

Fixes #5186
This commit is contained in:
Steven Smith 2022-03-16 23:53:46 -07:00 committed by Kris Nuttycombe
parent 315392ce3b
commit 445f4de0f4
7 changed files with 192 additions and 35 deletions

View File

@ -12,8 +12,10 @@ from test_framework.util import (
assert_raises_message,
connect_nodes_bi,
get_coinbase_address,
nuparams,
DEFAULT_FEE,
DEFAULT_FEE_ZATS
DEFAULT_FEE_ZATS,
NU5_BRANCH_ID,
)
from test_framework.util import wait_and_assert_operationid_status, start_nodes
from decimal import Decimal
@ -33,7 +35,12 @@ class ListReceivedTest (BitcoinTestFramework):
def setup_network(self):
self.nodes = start_nodes(
self.num_nodes, self.options.tmpdir,
extra_args=[['-experimentalfeatures', '-orchardwallet']] * self.num_nodes)
extra_args=[[
'-experimentalfeatures',
'-orchardwallet',
nuparams(NU5_BRANCH_ID, 225),
]] * self.num_nodes
)
connect_nodes_bi(self.nodes, 0, 1)
connect_nodes_bi(self.nodes, 1, 2)
connect_nodes_bi(self.nodes, 0, 2)
@ -435,9 +442,101 @@ class ListReceivedTest (BitcoinTestFramework):
assert_equal(r[1]['blockindex'], -1) # not yet mined
assert 'blocktime' in r[1]
def test_received_orchard(self, height):
self.generate_and_sync(height+1)
taddr = self.nodes[1].getnewaddress()
acct1 = self.nodes[1].z_getnewaccount()['account']
acct2 = self.nodes[1].z_getnewaccount()['account']
addrResO = self.nodes[1].z_getaddressforaccount(acct1, ['orchard'])
assert_equal(addrResO['pools'], ['orchard'])
uao = addrResO['unifiedaddress']
addrResSO = self.nodes[1].z_getaddressforaccount(acct2, ['sapling', 'orchard'])
assert_equal(addrResSO['pools'], ['sapling', 'orchard'])
uaso = addrResSO['unifiedaddress']
self.nodes[0].sendtoaddress(taddr, 4.0)
self.generate_and_sync(height+2)
opid = self.nodes[1].z_sendmany(taddr, [
{'address': uao, 'amount': 1, 'memo': my_memo},
{'address': uaso, 'amount': 2},
])
txid0 = wait_and_assert_operationid_status(self.nodes[1], opid)
self.sync_all()
# Decrypted transaction details should be correct, even though
# the transaction is still just in the mempool
pt = self.nodes[1].z_viewtransaction(txid0)
assert_equal(pt['txid'], txid0)
assert_equal(len(pt['spends']), 0)
assert_equal(len(pt['outputs']), 2)
# Outputs are not returned in a defined order but the amounts are deterministic
outputs = sorted(pt['outputs'], key=lambda x: x['valueZat'])
assert_equal(outputs[0]['type'], 'orchard')
assert_equal(outputs[0]['address'], uao)
assert_equal(outputs[0]['value'], Decimal('1'))
assert_equal(outputs[0]['valueZat'], 100000000)
assert_equal(outputs[0]['output'], 0)
assert_equal(outputs[0]['outgoing'], False)
assert_equal(outputs[0]['memo'], my_memo)
assert_equal(outputs[0]['memoStr'], my_memo_str)
assert_equal(outputs[1]['type'], 'orchard')
assert_equal(outputs[1]['address'], uaso)
assert_equal(outputs[1]['value'], Decimal('2'))
assert_equal(outputs[1]['valueZat'], 200000000)
assert_equal(outputs[1]['output'], 1)
assert_equal(outputs[1]['outgoing'], False)
assert_equal(outputs[1]['memo'], no_memo)
assert 'memoStr' not in outputs[1]
self.generate_and_sync(height+3)
opid = self.nodes[1].z_sendmany(uao, [
{'address': uaso, 'amount': Decimal('0.4')}
])
txid1 = wait_and_assert_operationid_status(self.nodes[1], opid)
self.sync_all()
pt = self.nodes[1].z_viewtransaction(txid1)
assert_equal(pt['txid'], txid1)
assert_equal(len(pt['spends']), 1) # one spend we can see
assert_equal(len(pt['outputs']), 2) # one output + one change output we can see
spends = pt['spends']
assert_equal(spends[0]['type'], 'orchard')
assert_equal(spends[0]['spend'], 0)
assert_equal(spends[0]['txidPrev'], txid0)
assert_equal(spends[0]['outputPrev'], 0)
assert_equal(spends[0]['address'], uao)
assert_equal(spends[0]['value'], Decimal('1.0'))
assert_equal(spends[0]['valueZat'], 100000000)
outputs = sorted(pt['outputs'], key=lambda x: x['valueZat'])
assert_equal(outputs[0]['type'], 'orchard')
assert_equal(outputs[0]['address'], uaso)
assert_equal(outputs[0]['value'], Decimal('0.4'))
assert_equal(outputs[0]['valueZat'], 40000000)
assert_equal(outputs[0]['outgoing'], False)
assert_equal(outputs[0]['memo'], no_memo)
assert_equal(outputs[1]['type'], 'orchard')
# TODO: scrub change addresses
#assert_equal(outputs[1]['address'], '<change>')
assert_equal(outputs[1]['value'], Decimal('0.59999'))
assert_equal(outputs[1]['valueZat'], 59999000)
assert_equal(outputs[1]['outgoing'], False)
assert_equal(outputs[1]['memo'], no_memo)
def run_test(self):
self.test_received_sprout(200)
self.test_received_sapling(214)
self.test_received_orchard(230)
if __name__ == '__main__':

View File

@ -81,6 +81,11 @@ std::string SaplingOutPoint::ToString() const
return strprintf("SaplingOutPoint(%s, %u)", hash.ToString().substr(0, 10), n);
}
std::string OrchardOutPoint::ToString() const
{
return strprintf("OrchardOutPoint(%s, %u)", hash.ToString().substr(0, 10), n);
}
CTxIn::CTxIn(COutPoint prevoutIn, CScript scriptSigIn, uint32_t nSequenceIn)
{
prevout = prevoutIn;

View File

@ -272,6 +272,7 @@ struct RawOrchardActionSpend {
uint32_t spendActionIdx;
unsigned char outpointTxId[32];
uint32_t outpointActionIdx;
OrchardRawAddressPtr* receivedAt;
CAmount noteValue;
};

View File

@ -999,6 +999,7 @@ pub struct FFIActionSpend {
spend_action_idx: u32,
outpoint_txid: [u8; 32],
outpoint_action_idx: u32,
received_at: *mut Address,
value: i64,
}
@ -1070,6 +1071,7 @@ pub extern "C" fn orchard_wallet_get_txdata(
spend_action_idx: idx as u32,
outpoint_txid: *outpoint.txid.as_ref(),
outpoint_action_idx: outpoint.action_idx as u32,
received_at: Box::into_raw(Box::new(dnote.note.recipient())),
value: dnote.note.value().inner() as i64,
})
}) {

View File

@ -113,14 +113,20 @@ public:
class OrchardActionSpend {
private:
OrchardOutPoint outPoint;
libzcash::OrchardRawAddress receivedAt;
CAmount noteValue;
public:
OrchardActionSpend(OrchardOutPoint outPoint, CAmount noteValue): outPoint(outPoint), noteValue(noteValue) { }
OrchardActionSpend(OrchardOutPoint outPoint, libzcash::OrchardRawAddress receivedAt, CAmount noteValue):
outPoint(outPoint), receivedAt(receivedAt), noteValue(noteValue) { }
OrchardOutPoint GetOutPoint() const {
return outPoint;
}
const libzcash::OrchardRawAddress& GetReceivedAt() const {
return receivedAt;
}
CAmount GetNoteValue() const {
return noteValue;
}
@ -137,7 +143,7 @@ public:
libzcash::OrchardRawAddress recipient, CAmount noteValue, std::array<unsigned char, 512> memo, bool isOutgoing):
recipient(recipient), noteValue(noteValue), memo(memo), isOutgoing(isOutgoing) { }
const libzcash::OrchardRawAddress& GetRecipient() {
const libzcash::OrchardRawAddress& GetRecipient() const {
return recipient;
}
@ -428,6 +434,7 @@ public:
std::move(std::begin(rawSpend.outpointTxId), std::end(rawSpend.outpointTxId), txid.begin());
auto spend = OrchardActionSpend(
OrchardOutPoint(txid, rawSpend.outpointActionIdx),
libzcash::OrchardRawAddress(rawSpend.receivedAt),
rawSpend.noteValue);
reinterpret_cast<OrchardActions*>(receiver)->AddSpend(rawSpend.spendActionIdx, spend);
}

View File

@ -59,6 +59,7 @@ using namespace libzcash;
const std::string ADDR_TYPE_SPROUT = "sprout";
const std::string ADDR_TYPE_SAPLING = "sapling";
const std::string ADDR_TYPE_ORCHARD = "orchard";
extern UniValue TxJoinSplitToJSON(const CTransaction& tx);
@ -4073,15 +4074,15 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
LOCK2(cs_main, pwalletMain->cs_wallet);
uint256 hash;
hash.SetHex(params[0].get_str());
uint256 txid;
txid.SetHex(params[0].get_str());
UniValue entry(UniValue::VOBJ);
if (!pwalletMain->mapWallet.count(hash))
if (!pwalletMain->mapWallet.count(txid))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
const CWalletTx& wtx = pwalletMain->mapWallet[hash];
const CWalletTx& wtx = pwalletMain->mapWallet[txid];
entry.pushKV("txid", hash.GetHex());
entry.pushKV("txid", txid.GetHex());
UniValue spends(UniValue::VARR);
UniValue outputs(UniValue::VARR);
@ -4220,16 +4221,11 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
assert(pwalletMain->GetSaplingFullViewingKey(wtxPrev.mapSaplingNoteData.at(op).ivk, extfvk));
ovks.insert(extfvk.fvk.ovk);
// If the note belongs to a Sapling address that is part of an account in the
// wallet, show the corresponding Unified Address.
std::string address = keyIO.EncodePaymentAddress([&]() {
auto ua = pwalletMain->FindUnifiedAddressByReceiver(pa);
if (ua.has_value()) {
return libzcash::PaymentAddress{ua.value()};
} else {
return libzcash::PaymentAddress{pa};
}
}());
// Show the address that was cached at transaction construction as the
// recipient.
std::string address = keyIO.EncodePaymentAddress(
pwalletMain->GetPaymentAddressForRecipient(txid, pa)
);
UniValue entry(UniValue::VOBJ);
entry.pushKV("type", ADDR_TYPE_SAPLING);
@ -4244,7 +4240,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
// Sapling outputs
for (uint32_t i = 0; i < wtx.vShieldedOutput.size(); ++i) {
auto op = SaplingOutPoint(hash, i);
auto op = SaplingOutPoint(txid, i);
SaplingNotePlaintext notePt;
SaplingPaymentAddress pa;
@ -4274,16 +4270,11 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
}
auto memo = notePt.memo();
// If the note belongs to a Sapling address that is part of an account in the
// wallet, show the corresponding Unified Address.
std::string address = keyIO.EncodePaymentAddress([&]() {
auto ua = pwalletMain->FindUnifiedAddressByReceiver(pa);
if (ua.has_value()) {
return libzcash::PaymentAddress{ua.value()};
} else {
return libzcash::PaymentAddress{pa};
}
}());
// Show the address that was cached at transaction construction as the
// recipient.
std::string address = keyIO.EncodePaymentAddress(
pwalletMain->GetPaymentAddressForRecipient(txid, pa)
);
UniValue entry(UniValue::VOBJ);
entry.pushKV("type", ADDR_TYPE_SAPLING);
@ -4295,7 +4286,57 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
addMemo(entry, memo);
outputs.push_back(entry);
}
// TODO unified addresses, orchard, see #5186
std::vector<uint256> ovksVector(ovks.begin(), ovks.end());
OrchardActions orchardActions = pwalletMain->GetOrchardWallet().GetTxActions(wtx, ovksVector);
// Orchard spends
for (auto & pair : orchardActions.GetSpends()) {
// TODO test with Orchard spends after rebased on feature/wallet-orchard
auto actionIdx = pair.first;
OrchardActionSpend orchardActionSpend = pair.second;
auto outpoint = orchardActionSpend.GetOutPoint();
auto receivedAt = orchardActionSpend.GetReceivedAt();
auto noteValue = orchardActionSpend.GetNoteValue();
// if mapWallet doesn't contain the spent outpoint's txid,
// the wallet is corrupt, so using `.at` here is fine.
UniValue entry(UniValue::VOBJ);
entry.pushKV("type", ADDR_TYPE_ORCHARD);
entry.pushKV("spend", (int) actionIdx);
entry.pushKV("txidPrev", outpoint.hash.GetHex());
entry.pushKV("outputPrev", (int) outpoint.n);
auto ua = pwalletMain->FindUnifiedAddressByReceiver(receivedAt);
assert(ua.has_value());
std::string addrStr = keyIO.EncodePaymentAddress(ua.value());
entry.pushKV("address", addrStr);
entry.pushKV("value", ValueFromAmount(noteValue));
entry.pushKV("valueZat", noteValue);
spends.push_back(entry);
}
// Orchard outputs
for (const auto& [actionIdx, orchardActionOutput] : orchardActions.GetOutputs()) {
auto noteValue = orchardActionOutput.GetNoteValue();
auto recipient = orchardActionOutput.GetRecipient();
auto memo = orchardActionOutput.GetMemo();
// Show the address that was cached at transaction construction as the
// recipient.
std::string address = keyIO.EncodePaymentAddress(
pwalletMain->GetPaymentAddressForRecipient(txid, recipient)
);
UniValue entry(UniValue::VOBJ);
entry.pushKV("type", ADDR_TYPE_ORCHARD);
entry.pushKV("output", (int) actionIdx);
entry.pushKV("outgoing", orchardActionOutput.IsOutgoing());
entry.pushKV("address", address);
entry.pushKV("value", ValueFromAmount(noteValue));
entry.pushKV("valueZat", noteValue);
addMemo(entry, memo);
outputs.push_back(entry);
}
entry.pushKV("spends", spends);
entry.pushKV("outputs", outputs);
@ -4625,7 +4666,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
}
if (!recipientAddrs.insert(addr.value()).second) {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated recipient from address: ") + addrStr);
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated recipient address: ") + addrStr);
}
UniValue memoValue = find_value(o, "memo");

View File

@ -1190,6 +1190,8 @@ public:
pwalletdbEncryption = NULL;
}
OrchardWallet& GetOrchardWallet() { return orchardWallet; }
void SetNull(const CChainParams& params)
{
nWalletVersion = FEATURE_BASE;