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:
parent
7fce16b3c1
commit
c1fce47c5f
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
||||
/******************************************************************************************************************/
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user