Add Orchard cases to note selection logic

This commit is contained in:
Jack Grigg 2022-03-14 16:00:11 +00:00
parent f2ae807891
commit d4eee99060
4 changed files with 298 additions and 59 deletions

View File

@ -8,6 +8,7 @@
void PrintTo(const OutputPool& pool, std::ostream* os) {
switch (pool) {
case OutputPool::Orchard: *os << "Orchard"; break;
case OutputPool::Sapling: *os << "Sapling"; break;
case OutputPool::Transparent: *os << "Transparent"; break;
}
@ -28,6 +29,18 @@ SpendableInputs FakeSpendableInputs(
{
SpendableInputs inputs;
if (available.count(OutputPool::Orchard)) {
auto seed = MnemonicSeed::Random(0);
auto sk = libzcash::OrchardSpendingKey::ForAccount(seed, 0, 0);
libzcash::diversifier_index_t j(0);
auto address = sk.ToFullViewingKey().ToIncomingViewingKey().Address(j);
for (int i = 0; i < 10; i++) {
OrchardOutPoint op;
inputs.orchardNoteMetadata.push_back(OrchardNoteMetadata{
op, address, 1, {}});
}
}
if (available.count(OutputPool::Sapling)) {
for (int i = 0; i < 10; i++) {
SaplingOutPoint op;
@ -86,15 +99,17 @@ TEST_P(SpendableInputsTest, SelectsSproutBeforeFirst)
auto order = std::get<2>(GetParam());
auto wtx = FakeWalletTx();
bool canSelectSprout = !(recipientPools.count(OutputPool::Orchard));
// Create a set of inputs from Sprout and the available pools.
auto inputs = FakeSpendableInputs(available, true, &wtx);
EXPECT_EQ(inputs.Total(), 10 * (available.size() + 1));
// We have Sprout notes along with the expected notes.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 10);
for (auto pool : available) {
switch (pool) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), 10); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), 10); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), 10); break;
}
@ -104,13 +119,25 @@ TEST_P(SpendableInputsTest, SelectsSproutBeforeFirst)
EXPECT_TRUE(inputs.LimitToAmount(5, 1, recipientPools));
EXPECT_EQ(inputs.Total(), 5);
// We only have Sprout notes.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 5);
for (auto pool : order[0]) {
switch (pool) {
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), 0); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), 0); break;
if (canSelectSprout) {
// We only have Sprout notes.
EXPECT_EQ(inputs.sproutNoteEntries.size(), 5);
for (auto pool : order[0]) {
switch (pool) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), 0); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), 0); break;
}
}
} else {
// We never have Sprout notes.
EXPECT_EQ(inputs.sproutNoteEntries.size(), 0);
for (auto pool : order[0]) {
switch (pool) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), 5); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), 5); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), 5); break;
}
}
}
}
@ -122,32 +149,67 @@ TEST_P(SpendableInputsTest, SelectsSproutThenFirst)
auto order = std::get<2>(GetParam());
auto wtx = FakeWalletTx();
bool canSelectSprout = !(recipientPools.count(OutputPool::Orchard));
// Create a set of inputs from Sprout and the available pools.
auto inputs = FakeSpendableInputs(available, true, &wtx);
EXPECT_EQ(inputs.Total(), 10 * (available.size() + 1));
// We have Sprout notes along with the expected notes.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 10);
for (auto pool : available) {
switch (pool) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), 10); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), 10); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), 10); break;
}
}
// Limit to 14 zatoshis (which requires two pools).
EXPECT_TRUE(inputs.LimitToAmount(14, 1, std::get<1>(GetParam())));
EXPECT_EQ(inputs.Total(), 14);
// Limit to 14 zatoshis (which requires two pools). If we only have one pool
// available and can't select Sprout, we won't have sufficient funds.
auto sufficientFunds = inputs.LimitToAmount(14, 1, std::get<1>(GetParam()));
if (available.size() == 1 && !canSelectSprout) {
EXPECT_FALSE(sufficientFunds);
EXPECT_EQ(inputs.Total(), 10);
// We have all Sprout notes and some from the first pool in the first order.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 10);
for (int i = 0; i < order[0].size(); i++) {
auto expected = i == 0 ? 4 : 0;
switch (order[0][i]) {
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), expected); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), expected); break;
// We have no Sprout notes and all from the first pool in the first order.
EXPECT_EQ(inputs.sproutNoteEntries.size(), 0);
for (int i = 0; i < order[0].size(); i++) {
auto expected = i == 0 ? 10 : 0;
switch (order[0][i]) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), expected); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), expected); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), expected); break;
}
}
} else {
EXPECT_TRUE(sufficientFunds);
EXPECT_EQ(inputs.Total(), 14);
if (canSelectSprout) {
// We have all Sprout notes and some from the first pool in the
// first order.
EXPECT_EQ(inputs.sproutNoteEntries.size(), 10);
for (int i = 0; i < order[0].size(); i++) {
auto expected = i == 0 ? 4 : 0;
switch (order[0][i]) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), expected); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), expected); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), expected); break;
}
}
} else {
// We have all notes from the first pool and some from the second,
// in the second order.
EXPECT_EQ(inputs.sproutNoteEntries.size(), 0);
for (int i = 0; i < order[1].size(); i++) {
auto expected = i == 0 ? 10 : i == 1 ? 4 : 0;
switch (order[1][i]) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), expected); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), expected); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), expected); break;
}
}
}
}
}
@ -164,10 +226,10 @@ TEST_P(SpendableInputsTest, SelectsFirstBeforeSecond)
EXPECT_EQ(inputs.Total(), 10 * available.size());
// We have the expected notes.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 0);
for (auto pool : available) {
switch (pool) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), 10); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), 10); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), 10); break;
}
@ -178,11 +240,11 @@ TEST_P(SpendableInputsTest, SelectsFirstBeforeSecond)
EXPECT_EQ(inputs.Total(), 8);
// We use the first order and only have the first pool selected.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 0);
for (int i = 0; i < order[0].size(); i++) {
auto expected = i == 0 ? 8 : 0;
switch (order[0][i]) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), expected); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), expected); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), expected); break;
}
@ -201,10 +263,10 @@ TEST_P(SpendableInputsTest, SelectsFirstThenSecond)
EXPECT_EQ(inputs.Total(), 10 * available.size());
// We have the expected notes.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 0);
for (auto pool : available) {
switch (pool) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), 10); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), 10); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), 10); break;
}
@ -218,10 +280,10 @@ TEST_P(SpendableInputsTest, SelectsFirstThenSecond)
EXPECT_EQ(inputs.Total(), 10);
// We have selected all of the available pool.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 0);
for (int i = 0; i < order[0].size(); i++) {
switch (order[0][i]) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), 10); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), 10); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), 10); break;
}
@ -231,11 +293,11 @@ TEST_P(SpendableInputsTest, SelectsFirstThenSecond)
EXPECT_EQ(inputs.Total(), 13);
// We have all of the first pool and some of the second.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 0);
for (int i = 0; i < order[1].size(); i++) {
auto expected = i == 0 ? 10 : i == 1 ? 3 : 0;
switch (order[1][i]) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), expected); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), expected); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), expected); break;
}
@ -250,32 +312,34 @@ TEST_P(SpendableInputsTest, SelectsSproutAndFirstThenSecond)
auto order = std::get<2>(GetParam());
auto wtx = FakeWalletTx();
bool canSelectSprout = !(recipientPools.count(OutputPool::Orchard));
// Create a set of inputs from Sprout and the available pools.
auto inputs = FakeSpendableInputs(available, true, &wtx);
EXPECT_EQ(inputs.Total(), 10 * (available.size() + 1));
// We have Sprout notes along with the expected notes.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 10);
for (auto pool : available) {
switch (pool) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), 10); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), 10); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), 10); break;
}
}
// Limit to 24 zatoshis.
// If we only have one pool available, we won't have sufficient funds.
// Limit to 24 zatoshis. If we only have one pool available, or we have two
// pools but can't select Sprout, we won't have sufficient funds.
auto sufficientFunds = inputs.LimitToAmount(24, 1, std::get<1>(GetParam()));
if (available.size() == 1) {
if (available.size() == 1 || (available.size() == 2 && !canSelectSprout)) {
EXPECT_FALSE(sufficientFunds);
EXPECT_EQ(inputs.Total(), 20);
EXPECT_EQ(inputs.Total(), (canSelectSprout || available.size() == 2) ? 20 : 10);
// We have selected all of the available pool.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 10);
EXPECT_EQ(inputs.sproutNoteEntries.size(), canSelectSprout ? 10 : 0);
for (int i = 0; i < order[0].size(); i++) {
switch (order[0][i]) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), 10); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), 10); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), 10); break;
}
@ -284,14 +348,29 @@ TEST_P(SpendableInputsTest, SelectsSproutAndFirstThenSecond)
EXPECT_TRUE(sufficientFunds);
EXPECT_EQ(inputs.Total(), 24);
// We have all of Sprout and the first pool, and some of the second.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 10);
for (int i = 0; i < order[1].size(); i++) {
auto expected = i == 0 ? 10 : i == 1 ? 4 : 0;
switch (order[1][i]) {
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), expected); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), expected); break;
if (canSelectSprout) {
// We have all of Sprout and the first pool, and some of the second,
// in the second order.
EXPECT_EQ(inputs.sproutNoteEntries.size(), 10);
for (int i = 0; i < order[1].size(); i++) {
auto expected = i == 0 ? 10 : i == 1 ? 4 : 0;
switch (order[1][i]) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), expected); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), expected); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), expected); break;
}
}
} else {
// We have all notes from the first and second pools and some from
// the third, in the third order.
EXPECT_EQ(inputs.sproutNoteEntries.size(), 0);
for (int i = 0; i < order[2].size(); i++) {
auto expected = i <= 1 ? 10 : i == 2 ? 4 : 0;
switch (order[2][i]) {
case OutputPool::Orchard: EXPECT_EQ(inputs.orchardNoteMetadata.size(), expected); break;
case OutputPool::Sapling: EXPECT_EQ(inputs.saplingNoteEntries.size(), expected); break;
case OutputPool::Transparent: EXPECT_EQ(inputs.utxos.size(), expected); break;
}
}
}
}
@ -315,10 +394,15 @@ TEST_P(SpendableInputsTest, OpportunisticShielding)
EXPECT_EQ(inputs.Total(), 10 * available.size());
// Remove notes from the shielded pools, so we have more transparent funds.
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 0);
EXPECT_EQ(inputs.sproutNoteEntries.size(), 0);
for (auto pool : available) {
switch (pool) {
case OutputPool::Orchard:
while (inputs.orchardNoteMetadata.size() > 3) {
inputs.orchardNoteMetadata.pop_back();
}
EXPECT_EQ(inputs.orchardNoteMetadata.size(), 3);
break;
case OutputPool::Sapling:
while (inputs.saplingNoteEntries.size() > 3) {
inputs.saplingNoteEntries.pop_back();
@ -344,12 +428,19 @@ TEST_P(SpendableInputsTest, OpportunisticShielding)
const std::set<OutputPool> SET_T({OutputPool::Transparent});
const std::set<OutputPool> SET_S({OutputPool::Sapling});
const std::set<OutputPool> SET_O({OutputPool::Orchard});
const std::set<OutputPool> SET_TS({OutputPool::Transparent, OutputPool::Sapling});
const std::set<OutputPool> SET_TO({OutputPool::Transparent, OutputPool::Orchard});
const std::set<OutputPool> SET_SO({OutputPool::Sapling, OutputPool::Orchard});
const std::set<OutputPool> SET_TSO({OutputPool::Transparent, OutputPool::Sapling, OutputPool::Orchard});
const std::vector<OutputPool> VEC_T({OutputPool::Transparent});
const std::vector<OutputPool> VEC_S({OutputPool::Sapling});
const std::vector<OutputPool> VEC_O({OutputPool::Orchard});
const std::vector<OutputPool> VEC_TS({OutputPool::Transparent, OutputPool::Sapling});
const std::vector<OutputPool> VEC_ST({OutputPool::Sapling, OutputPool::Transparent});
const std::vector<OutputPool> VEC_TO({OutputPool::Transparent, OutputPool::Orchard});
const std::vector<OutputPool> VEC_SO({OutputPool::Sapling, OutputPool::Orchard});
const std::vector<OutputPool> VEC_TSO({OutputPool::Transparent, OutputPool::Sapling, OutputPool::Orchard});
INSTANTIATE_TEST_CASE_P(
ExhaustiveCases,
@ -359,12 +450,52 @@ INSTANTIATE_TEST_CASE_P(
// ----------|---------------------|-------------------//----------
std::make_tuple(SET_T, SET_T, std::vector({VEC_T})), // N/A
std::make_tuple(SET_T, SET_S, std::vector({VEC_T})), // N/A
std::make_tuple(SET_T, SET_O, std::vector({VEC_T})), // N/A
std::make_tuple(SET_T, SET_TS, std::vector({VEC_T})), // N/A
std::make_tuple(SET_T, SET_TO, std::vector({VEC_T})), // N/A
std::make_tuple(SET_T, SET_SO, std::vector({VEC_T})), // N/A
std::make_tuple(SET_T, SET_TSO, std::vector({VEC_T})), // N/A
std::make_tuple(SET_S, SET_T, std::vector({VEC_S})), // N/A
std::make_tuple(SET_S, SET_S, std::vector({VEC_S})), // N/A
std::make_tuple(SET_S, SET_O, std::vector({VEC_S})), // N/A
std::make_tuple(SET_S, SET_TS, std::vector({VEC_S})), // N/A
std::make_tuple(SET_S, SET_TO, std::vector({VEC_S})), // N/A
std::make_tuple(SET_S, SET_SO, std::vector({VEC_S})), // N/A
std::make_tuple(SET_S, SET_TSO, std::vector({VEC_S})), // N/A
std::make_tuple(SET_O, SET_T, std::vector({VEC_O})), // N/A
std::make_tuple(SET_O, SET_S, std::vector({VEC_O})), // N/A
std::make_tuple(SET_O, SET_O, std::vector({VEC_O})), // N/A
std::make_tuple(SET_O, SET_TS, std::vector({VEC_O})), // N/A
std::make_tuple(SET_O, SET_TO, std::vector({VEC_O})), // N/A
std::make_tuple(SET_O, SET_SO, std::vector({VEC_O})), // N/A
std::make_tuple(SET_O, SET_TSO, std::vector({VEC_O})), // N/A
std::make_tuple(SET_TS, SET_T, std::vector({VEC_S, VEC_TS})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TS, SET_S, std::vector({VEC_S, VEC_TS})), // Fully shielded, opportunistic shielding
std::make_tuple(SET_TS, SET_TS, std::vector({VEC_S, VEC_TS})) // Hide sender, opportunistic shielding
std::make_tuple(SET_TS, SET_O, std::vector({VEC_S, VEC_TS})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TS, SET_TS, std::vector({VEC_S, VEC_TS})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TS, SET_TO, std::vector({VEC_S, VEC_TS})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TS, SET_SO, std::vector({VEC_S, VEC_TS})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TS, SET_TSO, std::vector({VEC_S, VEC_TS})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TO, SET_T, std::vector({VEC_O, VEC_TO})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TO, SET_S, std::vector({VEC_O, VEC_TO})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TO, SET_O, std::vector({VEC_O, VEC_TO})), // Fully shielded, opportunistic shielding
std::make_tuple(SET_TO, SET_TS, std::vector({VEC_O, VEC_TO})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TO, SET_TO, std::vector({VEC_O, VEC_TO})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TO, SET_SO, std::vector({VEC_O, VEC_TO})), // Hide sender, opportunistic shielding
std::make_tuple(SET_TO, SET_TSO, std::vector({VEC_O, VEC_TO})), // Hide sender, opportunistic shielding
std::make_tuple(SET_SO, SET_T, std::vector({VEC_O, VEC_SO})), // Fewer pools, opportunistic migration
std::make_tuple(SET_SO, SET_S, std::vector({VEC_S, VEC_SO})), // Fully shielded
std::make_tuple(SET_SO, SET_O, std::vector({VEC_O, VEC_SO})), // Fully shielded, opportunistic migration
std::make_tuple(SET_SO, SET_TS, std::vector({VEC_S, VEC_SO})), // Fewer pools
std::make_tuple(SET_SO, SET_TO, std::vector({VEC_O, VEC_SO})), // Fewer pools, opportunistic migration
std::make_tuple(SET_SO, SET_SO, std::vector({VEC_S, VEC_SO})), // Opportunistic migration
std::make_tuple(SET_SO, SET_TSO, std::vector({VEC_S, VEC_SO})), // Opportunistic migration
std::make_tuple(SET_TSO, SET_T, std::vector({VEC_O, VEC_SO, VEC_TSO})), // Fewer pools, hide sender, opportunistic shielding
std::make_tuple(SET_TSO, SET_S, std::vector({VEC_S, VEC_SO, VEC_TSO})), // Fully shielded, hide sender, opportunistic shielding
std::make_tuple(SET_TSO, SET_O, std::vector({VEC_O, VEC_SO, VEC_TSO})), // Fully shielded, hide sender, opportunistic shielding
std::make_tuple(SET_TSO, SET_TS, std::vector({VEC_S, VEC_SO, VEC_TSO})), // Fewer pools, hide sender, opportunistic shielding
std::make_tuple(SET_TSO, SET_TO, std::vector({VEC_O, VEC_SO, VEC_TSO})), // Fewer pools, hide sender, opportunistic shielding
std::make_tuple(SET_TSO, SET_SO, std::vector({VEC_S, VEC_SO, VEC_TSO})), // Opportunistic migration, hide sender, opportunistic shielding
std::make_tuple(SET_TSO, SET_TSO, std::vector({VEC_S, VEC_SO, VEC_TSO})) // Opportunistic migration, hide sender, opportunistic shielding
)
);

View File

@ -53,6 +53,10 @@ public:
CAmount GetNoteValue() const {
return noteValue;
}
const std::array<uint8_t, ZC_MEMO_SIZE>& GetMemo() const {
return memo;
}
};
/**

View File

@ -7086,18 +7086,23 @@ bool SpendableInputs::LimitToAmount(
return totalWithExtra == amountRequired || totalWithExtra - amountRequired > dustThreshold;
};
// Select Sprout notes for spending first - if possible, we want users to
// spend any notes that they still have in the Sprout pool.
std::sort(sproutNoteEntries.begin(), sproutNoteEntries.end(),
[](SproutNoteEntry i, SproutNoteEntry j) -> bool {
return i.note.value() > j.note.value();
});
auto sproutIt = sproutNoteEntries.begin();
while (sproutIt != sproutNoteEntries.end() && !haveSufficientFunds()) {
totalSelected += sproutIt->note.value();
++sproutIt;
if (recipientPools.count(OutputPool::Orchard)) {
// We cannot select Sprout notes with Orchard recipients.
sproutNoteEntries.clear();
} else {
// Select Sprout notes for spending first - if possible, we want users to
// spend any notes that they still have in the Sprout pool.
std::sort(sproutNoteEntries.begin(), sproutNoteEntries.end(),
[](SproutNoteEntry i, SproutNoteEntry j) -> bool {
return i.note.value() > j.note.value();
});
auto sproutIt = sproutNoteEntries.begin();
while (sproutIt != sproutNoteEntries.end() && !haveSufficientFunds()) {
totalSelected += sproutIt->note.value();
++sproutIt;
}
sproutNoteEntries.erase(sproutIt, sproutNoteEntries.end());
}
sproutNoteEntries.erase(sproutIt, sproutNoteEntries.end());
// Check what input pools we have available.
CAmount availableTransparent = std::accumulate(
@ -7111,10 +7116,19 @@ bool SpendableInputs::LimitToAmount(
[](CAmount acc, const SaplingNoteEntry& entry) {
return acc + entry.note.value();
});
CAmount availableOrchard = std::accumulate(
orchardNoteMetadata.begin(),
orchardNoteMetadata.end(),
CAmount(0),
[](CAmount acc, const OrchardNoteMetadata& entry) {
return acc + entry.GetNoteValue();
});
assert(availableTransparent >= 0);
assert(availableSapling >= 0);
assert(availableOrchard >= 0);
bool haveTransparent = availableTransparent > 0;
bool haveSapling = availableSapling > 0;
bool haveOrchard = availableOrchard > 0;
std::set<OutputPool> available;
if (haveTransparent) {
available.insert(OutputPool::Transparent);
@ -7122,6 +7136,9 @@ bool SpendableInputs::LimitToAmount(
if (haveSapling) {
available.insert(OutputPool::Sapling);
}
if (haveOrchard) {
available.insert(OutputPool::Orchard);
}
// Now determine the order in which to select the remaining notes and coins.
// We do this in a way that minimizes information leakage while moving funds
@ -7150,23 +7167,77 @@ bool SpendableInputs::LimitToAmount(
// used is the first order in the list that can select sufficient funds.
// - T: transparent pool
// - S: Sapling pool
// - O: Orchard pool
//
// Available | Recipients | Order | Rationale
// ----------|------------|--------|----------
// T | ** | T | N/A
// S | ** | S | N/A
// T | *** | T | N/A
// S | *** | S | N/A
// O | *** | O | N/A
// TS | T | S, TS | Hide sender, opportunistic shielding
// TS | S | S, TS | Fully shielded, opportunistic shielding
// TS | O | S, TS | Hide sender, opportunistic shielding
// TS | TS | S, TS | Hide sender, opportunistic shielding
// TS | T O | S, TS | Hide sender, opportunistic shielding
// TS | SO | S, TS | Hide sender, opportunistic shielding
// TS | TSO | S, TS | Hide sender, opportunistic shielding
// T O | T | O, TO | Hide sender, opportunistic shielding
// T O | S | O, TO | Hide sender, opportunistic shielding
// T O | O | O, TO | Fully shielded, opportunistic shielding
// T O | TS | O, TO | Hide sender, opportunistic shielding
// T O | T O | O, TO | Hide sender, opportunistic shielding
// T O | SO | O, TO | Hide sender, opportunistic shielding
// T O | TSO | O, TO | Hide sender, opportunistic shielding
// SO | T | O, SO | Fewer pools, opportunistic migration
// SO | S | S, SO | Fully shielded
// SO | O | O, SO | Fully shielded, opportunistic migration
// SO | TS | S, SO | Fewer pools
// SO | T O | O, SO | Fewer pools, opportunistic migration
// SO | SO | S, SO | Opportunistic migration
// SO | TSO | S, SO | Opportunistic migration
// TSO | T | O, SO, TSO | Fewer pools, hide sender, opportunistic shielding
// TSO | S | S, SO, TSO | Fully shielded, hide sender, opportunistic shielding
// TSO | O | O, SO, TSO | Fully shielded, hide sender, opportunistic shielding
// TSO | TS | S, SO, TSO | Fewer pools, hide sender, opportunistic shielding
// TSO | T O | O, SO, TSO | Fewer pools, hide sender, opportunistic shielding
// TSO | SO | S, SO, TSO | Opportunistic migration, hide sender, opportunistic shielding
// TSO | TSO | S, SO, TSO | Opportunistic migration, hide sender, opportunistic shielding
std::vector<OutputPool> selectionOrder;
bool opportunisticShielding = false;
if (available.size() <= 1) {
// We have at most one input pool, so we don't need selection logic.
selectionOrder.assign(available.begin(), available.end());
} else if (wouldSuffice(availableSapling)) {
// Either fully-shielded, or we hide the sender.
} else if (
recipientPools == std::set({OutputPool::Orchard}) &&
wouldSuffice(availableOrchard))
{
// Fully shielded.
selectionOrder = {
OutputPool::Orchard,
// Pools below here are erased.
OutputPool::Transparent,
OutputPool::Sapling,
};
} else if (
recipientPools.count(OutputPool::Transparent) &&
!recipientPools.count(OutputPool::Sapling) &&
wouldSuffice(availableOrchard))
{
// Fewer pools.
selectionOrder = {
OutputPool::Orchard,
// Pools below here are erased.
OutputPool::Transparent,
OutputPool::Sapling,
};
} else if (wouldSuffice(availableSapling + availableOrchard)) {
// Hide sender.
// This case also handles two other cases:
// - Fully shielded (recipientPools == S && wouldSuffice(S))
// - Fewer pools (S in recipientPools && O not in recipientPools && wouldSuffice(S))
selectionOrder = {
OutputPool::Sapling,
OutputPool::Orchard,
// Pools below here are erased.
OutputPool::Transparent,
};
@ -7175,13 +7246,20 @@ bool SpendableInputs::LimitToAmount(
selectionOrder = {
OutputPool::Transparent,
OutputPool::Sapling,
OutputPool::Orchard,
};
opportunisticShielding = true;
}
// Ensure we provided a total selection order (so that all unselected notes
// and coins are erased).
assert(selectionOrder.size() == available.size());
for (auto pool : available) {
bool poolIsPresent = false;
for (auto entry : selectionOrder) {
poolIsPresent |= entry == pool;
}
assert(poolIsPresent);
}
// Finally, select the remaining notes and coins based on this order.
for (auto pool : selectionOrder) {
@ -7221,6 +7299,21 @@ bool SpendableInputs::LimitToAmount(
saplingNoteEntries.erase(saplingIt, saplingNoteEntries.end());
break;
}
case OutputPool::Orchard:
{
std::sort(orchardNoteMetadata.begin(), orchardNoteMetadata.end(),
[](OrchardNoteMetadata i, OrchardNoteMetadata j) -> bool {
return i.GetNoteValue() > j.GetNoteValue();
});
auto orchardIt = orchardNoteMetadata.begin();
while (orchardIt != orchardNoteMetadata.end() && !haveSufficientFunds()) {
totalSelected += orchardIt->GetNoteValue();
++orchardIt;
}
orchardNoteMetadata.erase(orchardIt, orchardNoteMetadata.end());
break;
}
}
}
@ -7269,4 +7362,14 @@ void SpendableInputs::LogInputs(const AsyncRPCOperationId& id) const {
FormatMoney(entry.note.value()),
HexStr(data).substr(0, 10));
}
for (const auto& entry : orchardNoteMetadata) {
std::string data(entry.GetMemo().begin(), entry.GetMemo().end());
LogPrint("zrpcunsafe", "%s: found unspent Orchard note (txid=%s, vActionsOrchard=%d, amount=%s, memo=%s)\n",
id,
entry.GetOutPoint().hash.ToString().substr(0, 10),
entry.GetOutPoint().n,
FormatMoney(entry.GetNoteValue()),
HexStr(data).substr(0, 10));
}
}

View File

@ -806,6 +806,7 @@ public:
* set of output pools the most-preferred pool is selected first.
*/
enum class OutputPool {
Orchard,
Sapling,
Transparent,
};