Add Orchard & unified address support to z_viewtransaction.
Fixes #5186
This commit is contained in:
parent
315392ce3b
commit
445f4de0f4
|
@ -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__':
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -272,6 +272,7 @@ struct RawOrchardActionSpend {
|
|||
uint32_t spendActionIdx;
|
||||
unsigned char outpointTxId[32];
|
||||
uint32_t outpointActionIdx;
|
||||
OrchardRawAddressPtr* receivedAt;
|
||||
CAmount noteValue;
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -1190,6 +1190,8 @@ public:
|
|||
pwalletdbEncryption = NULL;
|
||||
}
|
||||
|
||||
OrchardWallet& GetOrchardWallet() { return orchardWallet; }
|
||||
|
||||
void SetNull(const CChainParams& params)
|
||||
{
|
||||
nWalletVersion = FEATURE_BASE;
|
||||
|
|
Loading…
Reference in New Issue