Add Orchard cases to note selection logic
This commit is contained in:
parent
f2ae807891
commit
d4eee99060
|
@ -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
|
||||
)
|
||||
);
|
||||
|
|
|
@ -53,6 +53,10 @@ public:
|
|||
CAmount GetNoteValue() const {
|
||||
return noteValue;
|
||||
}
|
||||
|
||||
const std::array<uint8_t, ZC_MEMO_SIZE>& GetMemo() const {
|
||||
return memo;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -806,6 +806,7 @@ public:
|
|||
* set of output pools the most-preferred pool is selected first.
|
||||
*/
|
||||
enum class OutputPool {
|
||||
Orchard,
|
||||
Sapling,
|
||||
Transparent,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue