parent
fe384eeb1f
commit
8617622e0d
|
@ -3,9 +3,11 @@
|
|||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_message,
|
||||
start_nodes,
|
||||
)
|
||||
|
||||
|
@ -26,6 +28,31 @@ class WalletAccountsTest(BitcoinTestFramework):
|
|||
account1 = self.nodes[0].z_getnewaccount()
|
||||
assert_equal(account1['account'], 1)
|
||||
|
||||
# Generate the first address for account 0.
|
||||
addr0 = self.nodes[0].z_getaddressforaccount(0)
|
||||
assert_equal(addr0['account'], 0)
|
||||
assert_equal(set(addr0['pools']), set(['transparent', 'sapling']))
|
||||
ua0 = addr0['unifiedaddress']
|
||||
|
||||
# We pick mnemonic phrases to ensure that we can always generate the default
|
||||
# address in account 0; this is however not necessarily at diversifier index 0.
|
||||
# We should be able to generate it directly and get the exact same data.
|
||||
j = addr0['diversifier_index']
|
||||
assert_equal(self.nodes[0].z_getaddressforaccount(0, j), addr0)
|
||||
if j > 0:
|
||||
# We should get an error if we generate the address at diversifier index 0.
|
||||
assert_raises_message(
|
||||
JSONRPCException,
|
||||
'no address at diversifier index 0',
|
||||
self.nodes[0].z_getaddressforaccount, 0, 0)
|
||||
|
||||
# The first address for account 1 is different to account 0.
|
||||
addr1 = self.nodes[0].z_getaddressforaccount(1)
|
||||
assert_equal(addr1['account'], 1)
|
||||
assert_equal(set(addr1['pools']), set(['transparent', 'sapling']))
|
||||
ua1 = addr1['unifiedaddress']
|
||||
assert(ua0 != ua1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletAccountsTest().main()
|
||||
|
|
|
@ -595,4 +595,30 @@ BOOST_AUTO_TEST_CASE(test_ParseArbitraryInt)
|
|||
BOOST_CHECK_EQUAL((*v)[21], 0x10);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_ArbitraryIntStr)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({}), "0");
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({0}), "0");
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({0, 0}), "0");
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({0, 0, 0}), "0");
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({0, 0, 0, 0}), "0");
|
||||
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({1}), "1");
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({2}), "2");
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({10}), "10");
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({100}), "100");
|
||||
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({0xff}), "255");
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({0x00, 0x01}), "256");
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({0xff, 0x01}), "511");
|
||||
BOOST_CHECK_EQUAL(ArbitraryIntStr({0x00, 0x02}), "512");
|
||||
|
||||
BOOST_CHECK_EQUAL(
|
||||
ArbitraryIntStr({0xff, 0xff, 0xff, 0xff}),
|
||||
"4294967295");
|
||||
BOOST_CHECK_EQUAL(
|
||||
ArbitraryIntStr({0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}),
|
||||
"309485009821345068724781055");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -64,6 +64,13 @@ std::string base_blob<BITS>::ToString() const
|
|||
return (GetHex());
|
||||
}
|
||||
|
||||
// Explicit instantiations for base_blob<88>
|
||||
template base_blob<88>::base_blob(const std::vector<unsigned char>&);
|
||||
template std::string base_blob<88>::GetHex() const;
|
||||
template std::string base_blob<88>::ToString() const;
|
||||
template void base_blob<88>::SetHex(const char*);
|
||||
template void base_blob<88>::SetHex(const std::string&);
|
||||
|
||||
// Explicit instantiations for base_blob<160>
|
||||
template base_blob<160>::base_blob(const std::vector<unsigned char>&);
|
||||
template std::string base_blob<160>::GetHex() const;
|
||||
|
|
|
@ -528,3 +528,38 @@ std::optional<std::vector<uint8_t>> ParseArbitraryInt(const std::string& num_str
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ArbitraryIntStr(std::vector<uint8_t> bytes)
|
||||
{
|
||||
// Only serialize up to the most significant non-zero byte.
|
||||
size_t end = bytes.size();
|
||||
for (; end > 0 && bytes[end - 1] == 0; --end) {}
|
||||
|
||||
std::string result;
|
||||
while (end > 0) {
|
||||
// "Divide" bytes by 10.
|
||||
uint16_t rem = 0;
|
||||
for (int i = end - 1; i >= 0; --i) {
|
||||
uint16_t tmp = rem * 256 + bytes[i];
|
||||
rem = tmp % 10;
|
||||
auto b = tmp / 10;
|
||||
assert(b < 256);
|
||||
bytes[i] = b;
|
||||
}
|
||||
|
||||
// Write out the remainder as the next lowest digit.
|
||||
result = tfm::format("%d%s", rem, result);
|
||||
|
||||
// If we've moved all the bits out of the MSB, drop it.
|
||||
if (bytes[end - 1] == 0) {
|
||||
end--;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the all-zero bytes case.
|
||||
if (result.empty()) {
|
||||
return "0";
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,5 +168,9 @@ bool ConvertBits(const O& outfn, I it, I end) {
|
|||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> ParseArbitraryInt(const std::string& s);
|
||||
/**
|
||||
* Serializes the given little-endian byte iterator to a decimal string.
|
||||
*/
|
||||
std::string ArbitraryIntStr(std::vector<uint8_t> i);
|
||||
|
||||
#endif // BITCOIN_UTILSTRENCODINGS_H
|
||||
|
|
|
@ -21,12 +21,14 @@
|
|||
#include "util.h"
|
||||
#include "util/match.h"
|
||||
#include "utilmoneystr.h"
|
||||
#include "utilstrencodings.h"
|
||||
#include "wallet.h"
|
||||
#include "walletdb.h"
|
||||
#include "primitives/transaction.h"
|
||||
#include "zcbenchmarks.h"
|
||||
#include "script/interpreter.h"
|
||||
#include "zcash/Address.hpp"
|
||||
#include "zcash/address/zip32.h"
|
||||
|
||||
#include "utiltime.h"
|
||||
#include "asyncrpcoperation.h"
|
||||
|
@ -3085,13 +3087,18 @@ UniValue z_getaddressforaccount(const UniValue& params, bool fHelp)
|
|||
if (!fExperimentalOrchardWallet) {
|
||||
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: the Orchard wallet experimental extensions are disabled.");
|
||||
}
|
||||
int64_t account = params[0].get_int64();
|
||||
if (account < 0 || account >= ZCASH_LEGACY_ACCOUNT) {
|
||||
|
||||
LOCK(pwalletMain->cs_wallet);
|
||||
|
||||
int64_t accountInt = params[0].get_int64();
|
||||
if (accountInt < 0 || accountInt >= ZCASH_LEGACY_ACCOUNT) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid account number, must be 0 <= account <= (2^31)-2.");
|
||||
}
|
||||
// TODO: Check that the account is known to the wallet.
|
||||
std::vector<uint8_t> parsed_diversifier_index;
|
||||
libzcash::AccountId account = accountInt;
|
||||
|
||||
std::optional<libzcash::diversifier_index_t> j = std::nullopt;
|
||||
if (params.size() >= 2) {
|
||||
// TODO: Handle '*'
|
||||
if (params[1].getType() != UniValue::VNUM) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid diversifier index, must be an unsigned integer.");
|
||||
}
|
||||
|
@ -3099,36 +3106,100 @@ UniValue z_getaddressforaccount(const UniValue& params, bool fHelp)
|
|||
if (!parsed_diversifier_index_opt.has_value()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "diversifier index must be a decimal integer.");
|
||||
}
|
||||
parsed_diversifier_index = parsed_diversifier_index_opt.value();
|
||||
auto parsed_diversifier_index = parsed_diversifier_index_opt.value();
|
||||
if (parsed_diversifier_index.size() > ZC_DIVERSIFIER_SIZE) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "diversifier index is too large.");
|
||||
}
|
||||
} else {
|
||||
// TODO get next unused diversifier index from wallet
|
||||
// Extend the byte array to the correct length for diversifier_index_t.
|
||||
parsed_diversifier_index.resize(ZC_DIVERSIFIER_SIZE);
|
||||
j = libzcash::diversifier_index_t(parsed_diversifier_index);
|
||||
}
|
||||
// TODO:
|
||||
// diversifier_t diversifier{};
|
||||
// std::copy(parsed_diversifier_index.begin(), parsed_diversifier_index.end(), diversifier.begin());
|
||||
UniValue pools(UniValue::VARR);
|
||||
|
||||
std::set<libzcash::ReceiverType> receivers;
|
||||
if (params.size() >= 3) {
|
||||
pools = params[2].get_array();
|
||||
const auto& pools = params[2].get_array();
|
||||
for (unsigned int i = 0; i < pools.size(); i++) {
|
||||
const std::string& p = pools[i].get_str();
|
||||
if (!(p == "transparent" || p == "sapling" || p == "orchard")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "pool arguments must be \"transparent\", \"sapling\", or \"orchard\"");
|
||||
if (p == "transparent") {
|
||||
receivers.insert(ReceiverType::P2PKH);
|
||||
} else if (p == "sapling") {
|
||||
receivers.insert(ReceiverType::Sapling);
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "pool arguments must be \"transparent\", or \"sapling\"");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// default is all
|
||||
pools.push_back("transparent");
|
||||
pools.push_back("sapling");
|
||||
pools.push_back("orchard");
|
||||
// Default is the best and second-best shielded pools, and the transparent pool.
|
||||
receivers = {ReceiverType::P2PKH, ReceiverType::Sapling};
|
||||
}
|
||||
|
||||
EnsureWalletIsUnlocked();
|
||||
|
||||
// Generate the first UA for this account, using the best and next-best shielded pools
|
||||
// and the transparent pool.
|
||||
auto res = pwalletMain->GenerateUnifiedAddress(account, receivers, j);
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("account", account);
|
||||
result.pushKV("diversifier_index", params[1].write());
|
||||
result.pushKV("account", (uint64_t)account);
|
||||
|
||||
std::visit(match {
|
||||
[&](std::pair<libzcash::UnifiedAddress, libzcash::diversifier_index_t> addr) {
|
||||
result.pushKV("unifiedaddress", KeyIO(Params()).EncodePaymentAddress(addr.first));
|
||||
UniValue j;
|
||||
j.setNumStr(ArbitraryIntStr(std::vector(addr.second.begin(), addr.second.end())));
|
||||
result.pushKV("diversifier_index", j);
|
||||
},
|
||||
[&](AddressGenerationError err) {
|
||||
std::string strErr;
|
||||
switch (err) {
|
||||
case AddressGenerationError::NoSuchAccount:
|
||||
strErr = tfm::format("Error: account %d has not been generated by z_getnewaccount.", account);
|
||||
break;
|
||||
case AddressGenerationError::ExistingAddressMismatch:
|
||||
strErr = tfm::format(
|
||||
"Error: address at diversifier index %s was already generated with different receiver types.",
|
||||
params[1].getValStr());
|
||||
break;
|
||||
case AddressGenerationError::NoAddressForDiversifier:
|
||||
strErr = tfm::format(
|
||||
"Error: no address at diversifier index %s.",
|
||||
ArbitraryIntStr(std::vector(j.value().begin(), j.value().end())));
|
||||
break;
|
||||
case AddressGenerationError::InvalidTransparentChildIndex:
|
||||
strErr = tfm::format(
|
||||
"Error: diversifier index %s cannot generate an address with a transparent receiver.",
|
||||
ArbitraryIntStr(std::vector(j.value().begin(), j.value().end())));
|
||||
break;
|
||||
default:
|
||||
// By construction, we will not see these errors here:
|
||||
// - InvalidReceiverTypes
|
||||
// - WalletEncrypted
|
||||
//
|
||||
// If we see these, the user either has generated many addresses, or
|
||||
// was very unlucky with their mnemonic phrase generation:
|
||||
// - DiversifierSpaceExhausted
|
||||
strErr = tfm::format("Error: ran out of diversifier indices. Generate a new account with z_getnewaccount");
|
||||
}
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, strErr);
|
||||
},
|
||||
}, res);
|
||||
|
||||
UniValue pools(UniValue::VARR);
|
||||
for (const auto& receiver : receivers) {
|
||||
switch (receiver) {
|
||||
case ReceiverType::P2PKH:
|
||||
pools.push_back("transparent");
|
||||
break;
|
||||
case ReceiverType::Sapling:
|
||||
pools.push_back("sapling");
|
||||
break;
|
||||
default:
|
||||
// Unreachable
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
result.pushKV("pools", pools);
|
||||
result.pushKV("unifiedaddress", "TODO");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue