3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-12 10:15:52 +00:00

Implement the ability to explicitly set a fee in the Wallet.SendRequest. Does not yet auto-calculate the correct fees. Resolves issue 45. Resolves issue 245.

This commit is contained in:
Mike Hearn 2012-09-06 17:45:04 +02:00
parent 915a2adb10
commit 572f2a4f4e
3 changed files with 43 additions and 15 deletions

View File

@ -1276,6 +1276,16 @@ public class Wallet implements Serializable {
*/
public Address changeAddress;
/**
* A transaction can have a fee attached, which is defined as the difference between the input values
* and output values. Any value taken in that is not provided to an output can be claimed by a miner. This
* is how mining is incentivized in later years of the Bitcoin system when inflation drops. It also provides
* a way for people to prioritize their transactions over others and is used as a way to make denial of service
* attacks expensive. Some transactions require a fee due to their structure - currently bitcoinj does not
* correctly calculate this! As of late 2012 most transactions require no fee.
*/
public BigInteger fee = BigInteger.ZERO;
// Tracks if this has been passed to wallet.completeTx already: just a safety check.
private boolean completed;
@ -1436,14 +1446,15 @@ public class Wallet implements Serializable {
*/
public synchronized boolean completeTx(SendRequest req) {
Preconditions.checkArgument(!req.completed, "Given SendRequest has already been completed.");
// Calculate the transaction total
BigInteger nanocoins = BigInteger.ZERO;
// Calculate the amount of value we need to import.
BigInteger value = BigInteger.ZERO;
for (TransactionOutput output : req.tx.getOutputs()) {
nanocoins = nanocoins.add(output.getValue());
value = value.add(output.getValue());
}
value = value.add(req.fee);
log.info("Completing send tx with {} outputs totalling {}",
req.tx.getOutputs().size(), bitcoinValueToFriendlyString(nanocoins));
req.tx.getOutputs().size(), bitcoinValueToFriendlyString(value));
// To send money to somebody else, we need to do gather up transactions with unspent outputs until we have
// sufficient value. Many coin selection algorithms are possible, we use a simple but suboptimal one.
@ -1461,18 +1472,18 @@ public class Wallet implements Serializable {
gathered.add(output);
valueGathered = valueGathered.add(output.getValue());
}
if (valueGathered.compareTo(nanocoins) >= 0) break;
if (valueGathered.compareTo(value) >= 0) break;
}
// Can we afford this?
if (valueGathered.compareTo(nanocoins) < 0) {
if (valueGathered.compareTo(value) < 0) {
log.info("Insufficient value in wallet for send, missing " +
bitcoinValueToFriendlyString(nanocoins.subtract(valueGathered)));
bitcoinValueToFriendlyString(value.subtract(valueGathered)));
// TODO: Should throw an exception here.
return false;
}
checkState(gathered.size() > 0);
req.tx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
BigInteger change = valueGathered.subtract(nanocoins);
BigInteger change = valueGathered.subtract(value);
if (change.compareTo(BigInteger.ZERO) > 0) {
// The value of the inputs is greater than what we want to send. Just like in real life then,
// we need to take back some coins ... this is called "change". Add another output that sends the change

View File

@ -58,18 +58,25 @@ public class WalletTest {
@Test
public void basicSpending() throws Exception {
// We'll set up a wallet that receives a coin, then sends a coin of lesser value and keeps the change.
// We'll set up a wallet that receives a coin, then sends a coin of lesser value and keeps the change. We
// will attach a small fee. Because the Bitcoin protocol makes it difficult to determine the fee of an
// arbitrary transaction in isolation, we'll check that the fee was set by examining the size of the change.
// Receive some money.
BigInteger v1 = Utils.toNanoCoins(1, 0);
Transaction t1 = createFakeTx(params, v1, myAddress);
wallet.receiveFromBlock(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
assertEquals(v1, wallet.getBalance());
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL));
ECKey k2 = new ECKey();
// Create a send with a fee.
Address destination = new ECKey().toAddress(params);
BigInteger v2 = toNanoCoins(0, 50);
Transaction t2 = wallet.createSend(k2.toAddress(params), v2);
Wallet.SendRequest req = Wallet.SendRequest.to(destination, v2);
req.fee = toNanoCoins(0, 1);
wallet.completeTx(req);
Transaction t2 = req.tx;
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL));
@ -77,6 +84,10 @@ public class WalletTest {
assertEquals(1, t2.getInputs().size());
assertEquals(myAddress, t2.getInputs().get(0).getScriptSig().getFromAddress());
assertEquals(t2.getConfidence().getConfidenceType(), TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
assertEquals(2, t2.getOutputs().size());
assertEquals(destination, t2.getOutputs().get(0).getScriptPubKey().getToAddress());
assertEquals(wallet.getChangeAddress(), t2.getOutputs().get(1).getScriptPubKey().getToAddress());
assertEquals(toNanoCoins(0, 49), t2.getOutputs().get(1).getValue());
// We have NOT proven that the signature is correct!

View File

@ -88,7 +88,7 @@ public class WalletTool {
" --output=1GthXFQMktFLWdh5EPNGqbq3H6WdG8zsWj:1.245\n" +
" If the output destination starts with 04 and is 65 bytes (130 chars) it will be\n" +
" treated as a public key instead of an address and the send will use \n" +
" <key> CHECKSIG as the script.\n" +
" <key> CHECKSIG as the script. You can also specify a --fee=0.01\n" +
"\n>>> WAITING\n" +
"You can wait for the condition specified by the --waitfor flag to become true. Transactions and new\n" +
@ -229,6 +229,7 @@ public class WalletTool {
parser.accepts("peers").withRequiredArg();
OptionSpec<String> outputFlag = parser.accepts("output").withRequiredArg();
parser.accepts("value").withRequiredArg();
parser.accepts("fee").withRequiredArg();
conditionFlag = parser.accepts("condition").withRequiredArg();
options = parser.parse(args);
@ -321,7 +322,11 @@ public class WalletTool {
System.err.println("You must specify at least one --output=addr:value.");
return;
}
send(outputFlag.values(options));
BigInteger fee = BigInteger.ZERO;
if (options.has("fee")) {
fee = Utils.toNanoCoins((String)options.valueOf("fee"));
}
send(outputFlag.values(options), fee);
break;
}
@ -343,7 +348,7 @@ public class WalletTool {
shutdown();
}
private static void send(List<String> outputs) {
private static void send(List<String> outputs, BigInteger fee) {
try {
// Convert the input strings to outputs.
Transaction t = new Transaction(params);
@ -377,6 +382,7 @@ public class WalletTool {
}
}
Wallet.SendRequest req = Wallet.SendRequest.forTx(t);
req.fee = fee;
if (!wallet.completeTx(req)) {
System.err.println("Insufficient funds: have " + wallet.getBalance());
return;