mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-07 14:54:15 +00:00
Add some unit tests for default coin selector that are more fine grained than what the WalletTest code provides.
This commit is contained in:
parent
2b4595c4f0
commit
628aba15f8
@ -21,6 +21,7 @@ import com.google.bitcoin.params.UnitTestParams;
|
||||
import com.google.bitcoin.store.BlockStore;
|
||||
import com.google.bitcoin.store.MemoryBlockStore;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
|
||||
@ -58,6 +59,7 @@ public class TestWithWallet {
|
||||
Wallet.SendRequest.DEFAULT_FEE_PER_KB = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Transaction sendMoneyToWallet(Wallet wallet, Transaction tx, AbstractBlockChain.NewBlockType type)
|
||||
throws IOException, VerificationException {
|
||||
if (type == null) {
|
||||
@ -70,26 +72,26 @@ public class TestWithWallet {
|
||||
if (type == AbstractBlockChain.NewBlockType.BEST_CHAIN)
|
||||
wallet.notifyNewBestBlock(bp.storedBlock);
|
||||
}
|
||||
return tx;
|
||||
return wallet.getTransaction(tx.getHash()); // Can be null if tx is a double spend that's otherwise irrelevant.
|
||||
}
|
||||
|
||||
protected Transaction sendMoneyToWallet(Transaction tx, AbstractBlockChain.NewBlockType type) throws IOException,
|
||||
ProtocolException, VerificationException {
|
||||
@Nullable
|
||||
protected Transaction sendMoneyToWallet(Transaction tx, AbstractBlockChain.NewBlockType type) throws IOException, VerificationException {
|
||||
return sendMoneyToWallet(this.wallet, tx, type);
|
||||
}
|
||||
|
||||
protected Transaction sendMoneyToWallet(Wallet wallet, BigInteger value, Address toAddress, AbstractBlockChain.NewBlockType type)
|
||||
throws IOException, ProtocolException, VerificationException {
|
||||
@Nullable
|
||||
protected Transaction sendMoneyToWallet(Wallet wallet, BigInteger value, Address toAddress, AbstractBlockChain.NewBlockType type) throws IOException, VerificationException {
|
||||
return sendMoneyToWallet(wallet, createFakeTx(params, value, toAddress), type);
|
||||
}
|
||||
|
||||
protected Transaction sendMoneyToWallet(Wallet wallet, BigInteger value, ECKey toPubKey, AbstractBlockChain.NewBlockType type)
|
||||
throws IOException, ProtocolException, VerificationException {
|
||||
@Nullable
|
||||
protected Transaction sendMoneyToWallet(Wallet wallet, BigInteger value, ECKey toPubKey, AbstractBlockChain.NewBlockType type) throws IOException, VerificationException {
|
||||
return sendMoneyToWallet(wallet, createFakeTx(params, value, toPubKey), type);
|
||||
}
|
||||
|
||||
protected Transaction sendMoneyToWallet(BigInteger value, AbstractBlockChain.NewBlockType type) throws IOException,
|
||||
ProtocolException, VerificationException {
|
||||
@Nullable
|
||||
protected Transaction sendMoneyToWallet(BigInteger value, AbstractBlockChain.NewBlockType type) throws IOException, VerificationException {
|
||||
return sendMoneyToWallet(this.wallet, createFakeTx(params, value, myAddress), type);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import com.google.bitcoin.core.NetworkParameters;
|
||||
import com.google.bitcoin.core.Transaction;
|
||||
import com.google.bitcoin.core.TransactionConfidence;
|
||||
import com.google.bitcoin.core.TransactionOutput;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
@ -23,31 +24,7 @@ public class DefaultCoinSelector implements CoinSelector {
|
||||
// When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting
|
||||
// them in order to improve performance.
|
||||
if (!biTarget.equals(NetworkParameters.MAX_MONEY)) {
|
||||
Collections.sort(sortedOutputs, new Comparator<TransactionOutput>() {
|
||||
public int compare(TransactionOutput a, TransactionOutput b) {
|
||||
int depth1 = 0;
|
||||
int depth2 = 0;
|
||||
TransactionConfidence conf1 = a.getParentTransaction().getConfidence();
|
||||
TransactionConfidence conf2 = b.getParentTransaction().getConfidence();
|
||||
if (conf1.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
|
||||
depth1 = conf1.getDepthInBlocks();
|
||||
if (conf2.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
|
||||
depth2 = conf2.getDepthInBlocks();
|
||||
BigInteger aValue = a.getValue();
|
||||
BigInteger bValue = b.getValue();
|
||||
BigInteger aCoinDepth = aValue.multiply(BigInteger.valueOf(depth1));
|
||||
BigInteger bCoinDepth = bValue.multiply(BigInteger.valueOf(depth2));
|
||||
int c1 = bCoinDepth.compareTo(aCoinDepth);
|
||||
if (c1 != 0) return c1;
|
||||
// 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.getParentTransaction().getHash().toBigInteger();
|
||||
BigInteger bHash = b.getParentTransaction().getHash().toBigInteger();
|
||||
return aHash.compareTo(bHash);
|
||||
}
|
||||
});
|
||||
sortOutputs(sortedOutputs);
|
||||
}
|
||||
// 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).
|
||||
@ -64,6 +41,34 @@ public class DefaultCoinSelector implements CoinSelector {
|
||||
return new CoinSelection(BigInteger.valueOf(total), selected);
|
||||
}
|
||||
|
||||
@VisibleForTesting static void sortOutputs(ArrayList<TransactionOutput> outputs) {
|
||||
Collections.sort(outputs, new Comparator<TransactionOutput>() {
|
||||
public int compare(TransactionOutput a, TransactionOutput b) {
|
||||
int depth1 = 0;
|
||||
int depth2 = 0;
|
||||
TransactionConfidence conf1 = a.getParentTransaction().getConfidence();
|
||||
TransactionConfidence conf2 = b.getParentTransaction().getConfidence();
|
||||
if (conf1.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
|
||||
depth1 = conf1.getDepthInBlocks();
|
||||
if (conf2.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
|
||||
depth2 = conf2.getDepthInBlocks();
|
||||
BigInteger aValue = a.getValue();
|
||||
BigInteger bValue = b.getValue();
|
||||
BigInteger aCoinDepth = aValue.multiply(BigInteger.valueOf(depth1));
|
||||
BigInteger bCoinDepth = bValue.multiply(BigInteger.valueOf(depth2));
|
||||
int c1 = bCoinDepth.compareTo(aCoinDepth);
|
||||
if (c1 != 0) return c1;
|
||||
// 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.getParentTransaction().getHash().toBigInteger();
|
||||
BigInteger bHash = b.getParentTransaction().getHash().toBigInteger();
|
||||
return aHash.compareTo(bHash);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. */
|
||||
protected boolean shouldSelect(Transaction tx) {
|
||||
return isSelectable(tx);
|
||||
@ -73,7 +78,8 @@ public class DefaultCoinSelector implements CoinSelector {
|
||||
// Only pick chain-included transactions, or transactions that are ours and pending.
|
||||
TransactionConfidence confidence = tx.getConfidence();
|
||||
TransactionConfidence.ConfidenceType type = confidence.getConfidenceType();
|
||||
if (type.equals(TransactionConfidence.ConfidenceType.BUILDING)) return true;
|
||||
if (type.equals(TransactionConfidence.ConfidenceType.BUILDING))
|
||||
return true;
|
||||
return type.equals(TransactionConfidence.ConfidenceType.PENDING) &&
|
||||
confidence.getSource().equals(TransactionConfidence.Source.SELF) &&
|
||||
confidence.numBroadcastPeers() > 1;
|
||||
|
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.bitcoin.wallet;
|
||||
|
||||
import com.google.bitcoin.core.*;
|
||||
import com.google.bitcoin.params.UnitTestParams;
|
||||
import com.google.bitcoin.utils.TestUtils;
|
||||
import com.google.bitcoin.utils.TestWithWallet;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DefaultCoinSelectorTest extends TestWithWallet {
|
||||
private static final NetworkParameters params = UnitTestParams.get();
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
@After
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectable() throws Exception {
|
||||
Transaction t;
|
||||
t = new Transaction(params);
|
||||
t.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.PENDING);
|
||||
assertFalse(DefaultCoinSelector.isSelectable(t));
|
||||
t.getConfidence().setSource(TransactionConfidence.Source.SELF);
|
||||
assertFalse(DefaultCoinSelector.isSelectable(t));
|
||||
t.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByName("1.2.3.4")));
|
||||
assertFalse(DefaultCoinSelector.isSelectable(t));
|
||||
t.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByName("5.6.7.8")));
|
||||
assertTrue(DefaultCoinSelector.isSelectable(t));
|
||||
t = new Transaction(params);
|
||||
t.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.BUILDING);
|
||||
assertTrue(DefaultCoinSelector.isSelectable(t));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void depthOrdering() throws Exception {
|
||||
// Send two transactions in two blocks on top of each other.
|
||||
Transaction t1 = checkNotNull(sendMoneyToWallet(Utils.COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN));
|
||||
Transaction t2 = checkNotNull(sendMoneyToWallet(Utils.COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN));
|
||||
|
||||
// Check we selected just the oldest one.
|
||||
DefaultCoinSelector selector = new DefaultCoinSelector();
|
||||
CoinSelection selection = selector.select(Utils.COIN, wallet.calculateAllSpendCandidates(true));
|
||||
assertTrue(selection.gathered.contains(t1.getOutputs().get(0)));
|
||||
assertEquals(Utils.COIN, selection.valueGathered);
|
||||
|
||||
// Check we ordered them correctly (by depth).
|
||||
ArrayList<TransactionOutput> candidates = new ArrayList<TransactionOutput>();
|
||||
candidates.add(t2.getOutput(0));
|
||||
candidates.add(t1.getOutput(0));
|
||||
DefaultCoinSelector.sortOutputs(candidates);
|
||||
assertEquals(t1.getOutput(0), candidates.get(0));
|
||||
assertEquals(t2.getOutput(0), candidates.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coinAgeOrdering() throws Exception {
|
||||
// Send three transactions in four blocks on top of each other. Coin age of t1 is 1*4=4, coin age of t2 = 2*2=4
|
||||
// and t3=0.01.
|
||||
Transaction t1 = checkNotNull(sendMoneyToWallet(Utils.COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN));
|
||||
// Padding block.
|
||||
wallet.notifyNewBestBlock(TestUtils.createFakeBlock(blockStore).storedBlock);
|
||||
final BigInteger TWO_COINS = Utils.COIN.multiply(BigInteger.valueOf(2));
|
||||
Transaction t2 = checkNotNull(sendMoneyToWallet(TWO_COINS, AbstractBlockChain.NewBlockType.BEST_CHAIN));
|
||||
Transaction t3 = checkNotNull(sendMoneyToWallet(Utils.CENT, AbstractBlockChain.NewBlockType.BEST_CHAIN));
|
||||
|
||||
// Should be ordered t2, t1, t3.
|
||||
ArrayList<TransactionOutput> candidates = new ArrayList<TransactionOutput>();
|
||||
candidates.add(t3.getOutput(0));
|
||||
candidates.add(t2.getOutput(0));
|
||||
candidates.add(t1.getOutput(0));
|
||||
DefaultCoinSelector.sortOutputs(candidates);
|
||||
assertEquals(t2.getOutput(0), candidates.get(0));
|
||||
assertEquals(t1.getOutput(0), candidates.get(1));
|
||||
assertEquals(t3.getOutput(0), candidates.get(2));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user