3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-13 10:45:51 +00:00

Added some wallet utility methods getTotalReceived() and getTotalSent() and a test for them.

This commit is contained in:
c 2015-11-27 18:38:05 -08:00 committed by Andreas Schildbach
parent 7fce16b3c1
commit c1fce47c5f
4 changed files with 176 additions and 1 deletions

View File

@ -268,6 +268,22 @@ public class Transaction extends ChildMessage {
return v;
}
/**
* Gets the sum of the inputs, regardless of who owns them.
*/
public Coin getInputSum() {
Coin inputTotal = Coin.ZERO;
for (TransactionInput input: inputs) {
Coin inputValue = input.getValue();
if (inputValue != null) {
inputTotal = inputTotal.add(inputValue);
}
}
return inputTotal;
}
/*
* If isSpent - check that all my outputs spent, otherwise check that there at least
* one unspent.
@ -382,6 +398,20 @@ public class Transaction extends ChildMessage {
return v;
}
/**
* Gets the sum of the outputs of the transaction. If the outputs are less than the inputs, it does not count the fee.
* @return the sum of the outputs regardless of who owns them.
*/
public Coin getOutputSum() {
Coin totalOut = Coin.ZERO;
for (TransactionOutput output: outputs) {
totalOut = totalOut.add(output.getValue());
}
return totalOut;
}
@Nullable private Coin cachedValue;
@Nullable private TransactionBag cachedForBag;

View File

@ -46,6 +46,7 @@ import org.spongycastle.crypto.params.*;
import javax.annotation.*;
import java.io.*;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
@ -601,7 +602,7 @@ public class Wallet extends BaseTaggableObject
return currentAddress(KeyChain.KeyPurpose.CHANGE);
}
/**
* @deprecated use {@link #currentChangeAddress()} instead.
* @deprecated use {@link #currentChangeAddress()} instead.
*/
public Address getChangeAddress() {
return currentChangeAddress();
@ -3225,6 +3226,82 @@ public class Wallet extends BaseTaggableObject
}
}
/**
* Returns the amount of bitcoin ever received via output. <b>This is not the balance!</b> If an output spends from a
* transaction whose inputs are also to our wallet, the input amounts are deducted from the outputs contribution, with a minimum of zero
* contribution. The idea behind this is we avoid double counting money sent to us.
* @return the total amount of satoshis received, regardless of whether it was spent or not.
*/
public Coin getTotalReceived() {
Coin total = Coin.ZERO;
// Include outputs to us if they were not just change outputs, ie the inputs to us summed to less
// than the outputs to us.
for (Transaction tx: transactions.values()) {
Coin txTotal = Coin.ZERO;
for (TransactionOutput output : tx.getOutputs()) {
if (output.isMine(this)) {
txTotal = txTotal.add(output.getValue());
}
}
for (TransactionInput in : tx.getInputs()) {
TransactionOutput prevOut = in.getConnectedOutput();
if (prevOut != null && prevOut.isMine(this)) {
txTotal = txTotal.subtract(prevOut.getValue());
}
}
if (txTotal.isPositive()) {
total = total.add(txTotal);
}
}
return total;
}
/**
* Returns the amount of bitcoin ever sent via output. If an output is sent to our own wallet, because of change or
* rotating keys or whatever, we do not count it. If the wallet was
* involved in a shared transaction, i.e. there is some input to the transaction that we don't have the key for, then
* we multiply the sum of the output values by the proportion of satoshi coming in to our inputs. Essentially we treat
* inputs as pooling into the transaction, becoming fungible and being equally distributed to all outputs.
* @return the total amount of satoshis sent by us
*/
public Coin getTotalSent() {
Coin total = Coin.ZERO;
for (Transaction tx: transactions.values()) {
// Count spent outputs to only if they were not to us. This means we don't count change outputs.
Coin txOutputTotal = Coin.ZERO;
for (TransactionOutput out : tx.getOutputs()) {
if (out.isMine(this) == false) {
txOutputTotal = txOutputTotal.add(out.getValue());
}
}
// Count the input values to us
Coin txOwnedInputsTotal = Coin.ZERO;
for (TransactionInput in : tx.getInputs()) {
TransactionOutput prevOut = in.getConnectedOutput();
if (prevOut != null && prevOut.isMine(this)) {
txOwnedInputsTotal = txOwnedInputsTotal.add(prevOut.getValue());
}
}
// If there is an input that isn't from us, i.e. this is a shared transaction
Coin txInputsTotal = tx.getInputSum();
if (txOwnedInputsTotal != txInputsTotal) {
// multiply our output total by the appropriate proportion to account for the inputs that we don't own
BigInteger txOutputTotalNum = new BigInteger(txOutputTotal.toString());
txOutputTotalNum = txOutputTotalNum.multiply(new BigInteger(txOwnedInputsTotal.toString()));
txOutputTotalNum = txOutputTotalNum.divide(new BigInteger(txInputsTotal.toString()));
txOutputTotal = Coin.valueOf(txOutputTotalNum.longValue());
}
total = total.add(txOutputTotal);
}
return total;
}
//endregion
/******************************************************************************************************************/

View File

@ -25,6 +25,7 @@ import org.bitcoinj.store.BlockStoreException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Random;
import static org.bitcoinj.core.Coin.*;
@ -51,6 +52,42 @@ public class FakeTxBuilder {
return roundTripTransaction(params, t);
}
/**
* Create a fake TX for unit tests, for use with unit tests that need greater control. One outputs, 2 random inputs,
* split randomly to create randomness.
*/
public static Transaction createFakeTxWithoutChangeAddress(NetworkParameters params, Coin value, Address to) {
Transaction t = new Transaction(params);
TransactionOutput outputToMe = new TransactionOutput(params, t, value, to);
t.addOutput(outputToMe);
// Make a random split in the output value so we get a distinct hash when we call this multiple times with same args
long split = new Random().nextLong();
if (split < 0) { split *= -1; }
if (split == 0) { split = 15; }
while (split > value.getValue()) {
split /= 2;
}
// Make a previous tx simply to send us sufficient coins. This prev tx is not really valid but it doesn't
// matter for our purposes.
Transaction prevTx1 = new Transaction(params);
TransactionOutput prevOut1 = new TransactionOutput(params, prevTx1, Coin.valueOf(split), to);
prevTx1.addOutput(prevOut1);
// Connect it.
t.addInput(prevOut1).setScriptSig(ScriptBuilder.createInputScript(TransactionSignature.dummy()));
// Fake signature.
// Do it again
Transaction prevTx2 = new Transaction(params);
TransactionOutput prevOut2 = new TransactionOutput(params, prevTx2, Coin.valueOf(value.getValue() - split), to);
prevTx2.addOutput(prevOut2);
t.addInput(prevOut2).setScriptSig(ScriptBuilder.createInputScript(TransactionSignature.dummy()));
// Serialize/deserialize to ensure internal state is stripped, as if it had been read from the wire.
return roundTripTransaction(params, t);
}
/**
* Create a fake TX of sufficient realism to exercise the unit tests. Two outputs, one to us, one to somewhere
* else to simulate change. There is one random input.

View File

@ -3185,4 +3185,35 @@ public class WalletTest extends TestWithWallet {
assertEquals(0, wallet.getTransactions(false).size());
assertEquals(0, wallet.getUnspents().size());
}
@Test
public void totalReceivedSent() throws Exception {
// Receive 4 BTC in 2 separate transactions
Transaction toMe1 = createFakeTxWithoutChangeAddress(params, COIN.multiply(2), myAddress);
Transaction toMe2 = createFakeTxWithoutChangeAddress(params, COIN.multiply(2), myAddress);
StoredBlock block1 = createFakeBlock(blockStore, toMe1, toMe2).storedBlock;
wallet.receiveFromBlock(toMe1, block1, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
wallet.receiveFromBlock(toMe2, block1, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
wallet.notifyNewBestBlock(block1);
// Check we calculate the total received correctly
assertEquals(Coin.COIN.multiply(4), wallet.getTotalReceived());
Address notMyAddr = new ECKey().toAddress(params);
// Send 3 BTC in a single transaction
SendRequest req = SendRequest.to(notMyAddr,Coin.COIN.multiply(3));
req.fee = CENT;
wallet.completeTx(req);
StoredBlock block2 = createFakeBlock(blockStore, req.tx).storedBlock;
wallet.receiveFromBlock(req.tx, block2, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
wallet.notifyNewBestBlock(block2);
// Check that we still have the same totalReceived, since the above tx will have sent us change back
assertEquals(Coin.COIN.multiply(4),wallet.getTotalReceived());
assertEquals(Coin.COIN.multiply(3),wallet.getTotalSent());
// TODO: test shared wallet calculation here
}
}