DefaultCoinSelector: extract compareByDepth() comparator

This commit is contained in:
Sean Gilligan 2023-09-06 12:14:18 -07:00 committed by Andreas Schildbach
parent 9eaff37897
commit 2be7ee33f8
2 changed files with 35 additions and 22 deletions

View file

@ -16,7 +16,6 @@
package org.bitcoinj.wallet; package org.bitcoinj.wallet;
import com.google.common.annotations.VisibleForTesting;
import org.bitcoinj.base.BitcoinNetwork; import org.bitcoinj.base.BitcoinNetwork;
import org.bitcoinj.base.Coin; import org.bitcoinj.base.Coin;
import org.bitcoinj.base.Network; import org.bitcoinj.base.Network;
@ -27,6 +26,7 @@ import org.bitcoinj.core.TransactionOutput;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
/** /**
@ -54,7 +54,7 @@ public class DefaultCoinSelector implements CoinSelector {
// When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting // When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting
// them in order to improve performance. // them in order to improve performance.
if (!target.equals(BitcoinNetwork.MAX_MONEY)) { if (!target.equals(BitcoinNetwork.MAX_MONEY)) {
sortOutputs(sortedOutputs); sortedOutputs.sort(DefaultCoinSelector::compareByDepth);
} }
// Now iterate over the sorted outputs until we have got as close to the target as possible or a little // Now iterate over the sorted outputs until we have got as close to the target as possible or a little
// bit over (excessive value will be change). // bit over (excessive value will be change).
@ -71,24 +71,37 @@ public class DefaultCoinSelector implements CoinSelector {
return new CoinSelection(selected); return new CoinSelection(selected);
} }
@VisibleForTesting static void sortOutputs(ArrayList<TransactionOutput> outputs) { /**
Collections.sort(outputs, (a, b) -> { * Comparator for sorting {@link TransactionOutput} by coin depth, value, and then hash.
int depth1 = a.getParentTransactionDepthInBlocks(); * @param a The first object to be compared
int depth2 = b.getParentTransactionDepthInBlocks(); * @param b The second object to be compared
Coin aValue = a.getValue(); * @return a negative integer, zero, or a positive integer as the first argument is
Coin bValue = b.getValue(); * less than, equal to, or greater than the second.
BigInteger aCoinDepth = BigInteger.valueOf(aValue.value).multiply(BigInteger.valueOf(depth1)); */
BigInteger bCoinDepth = BigInteger.valueOf(bValue.value).multiply(BigInteger.valueOf(depth2)); public static int compareByDepth(TransactionOutput a, TransactionOutput b) {
int c1 = bCoinDepth.compareTo(aCoinDepth); int depth1 = a.getParentTransactionDepthInBlocks();
if (c1 != 0) return c1; int depth2 = b.getParentTransactionDepthInBlocks();
// The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size. Coin aValue = a.getValue();
int c2 = bValue.compareTo(aValue); Coin bValue = b.getValue();
if (c2 != 0) return c2; BigInteger aCoinDepth = BigInteger.valueOf(aValue.value).multiply(BigInteger.valueOf(depth1));
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering. BigInteger bCoinDepth = BigInteger.valueOf(bValue.value).multiply(BigInteger.valueOf(depth2));
BigInteger aHash = a.getParentTransactionHash().toBigInteger(); int c1 = bCoinDepth.compareTo(aCoinDepth);
BigInteger bHash = b.getParentTransactionHash().toBigInteger(); if (c1 != 0) return c1;
return aHash.compareTo(bHash); // The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size.
}); int c2 = bValue.compareTo(aValue);
if (c2 != 0) return c2;
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering.
BigInteger aHash = a.getParentTransactionHash().toBigInteger();
BigInteger bHash = b.getParentTransactionHash().toBigInteger();
return aHash.compareTo(bHash);
};
/**
* @deprecated Use {@link #compareByDepth(TransactionOutput, TransactionOutput)} with {@link List#sort(Comparator)}
*/
@Deprecated
static void sortOutputs(ArrayList<TransactionOutput> outputs) {
Collections.sort(outputs, DefaultCoinSelector::compareByDepth);
} }
/** Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. */ /** Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. */

View file

@ -96,7 +96,7 @@ public class DefaultCoinSelectorTest extends TestWithWallet {
ArrayList<TransactionOutput> candidates = new ArrayList<>(); ArrayList<TransactionOutput> candidates = new ArrayList<>();
candidates.add(t2.getOutput(0)); candidates.add(t2.getOutput(0));
candidates.add(t1.getOutput(0)); candidates.add(t1.getOutput(0));
DefaultCoinSelector.sortOutputs(candidates); candidates.sort(DefaultCoinSelector::compareByDepth);
assertEquals(t1.getOutput(0), candidates.get(0)); assertEquals(t1.getOutput(0), candidates.get(0));
assertEquals(t2.getOutput(0), candidates.get(1)); assertEquals(t2.getOutput(0), candidates.get(1));
} }
@ -117,7 +117,7 @@ public class DefaultCoinSelectorTest extends TestWithWallet {
candidates.add(t3.getOutput(0)); candidates.add(t3.getOutput(0));
candidates.add(t2.getOutput(0)); candidates.add(t2.getOutput(0));
candidates.add(t1.getOutput(0)); candidates.add(t1.getOutput(0));
DefaultCoinSelector.sortOutputs(candidates); candidates.sort(DefaultCoinSelector::compareByDepth);
assertEquals(t2.getOutput(0), candidates.get(0)); assertEquals(t2.getOutput(0), candidates.get(0));
assertEquals(t1.getOutput(0), candidates.get(1)); assertEquals(t1.getOutput(0), candidates.get(1));
assertEquals(t3.getOutput(0), candidates.get(2)); assertEquals(t3.getOutput(0), candidates.get(2));