mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-12 18:25:51 +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:
parent
915a2adb10
commit
572f2a4f4e
@ -1276,6 +1276,16 @@ public class Wallet implements Serializable {
|
|||||||
*/
|
*/
|
||||||
public Address changeAddress;
|
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.
|
// Tracks if this has been passed to wallet.completeTx already: just a safety check.
|
||||||
private boolean completed;
|
private boolean completed;
|
||||||
|
|
||||||
@ -1436,14 +1446,15 @@ public class Wallet implements Serializable {
|
|||||||
*/
|
*/
|
||||||
public synchronized boolean completeTx(SendRequest req) {
|
public synchronized boolean completeTx(SendRequest req) {
|
||||||
Preconditions.checkArgument(!req.completed, "Given SendRequest has already been completed.");
|
Preconditions.checkArgument(!req.completed, "Given SendRequest has already been completed.");
|
||||||
// Calculate the transaction total
|
// Calculate the amount of value we need to import.
|
||||||
BigInteger nanocoins = BigInteger.ZERO;
|
BigInteger value = BigInteger.ZERO;
|
||||||
for (TransactionOutput output : req.tx.getOutputs()) {
|
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 {}",
|
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
|
// 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.
|
// 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);
|
gathered.add(output);
|
||||||
valueGathered = valueGathered.add(output.getValue());
|
valueGathered = valueGathered.add(output.getValue());
|
||||||
}
|
}
|
||||||
if (valueGathered.compareTo(nanocoins) >= 0) break;
|
if (valueGathered.compareTo(value) >= 0) break;
|
||||||
}
|
}
|
||||||
// Can we afford this?
|
// Can we afford this?
|
||||||
if (valueGathered.compareTo(nanocoins) < 0) {
|
if (valueGathered.compareTo(value) < 0) {
|
||||||
log.info("Insufficient value in wallet for send, missing " +
|
log.info("Insufficient value in wallet for send, missing " +
|
||||||
bitcoinValueToFriendlyString(nanocoins.subtract(valueGathered)));
|
bitcoinValueToFriendlyString(value.subtract(valueGathered)));
|
||||||
// TODO: Should throw an exception here.
|
// TODO: Should throw an exception here.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
checkState(gathered.size() > 0);
|
checkState(gathered.size() > 0);
|
||||||
req.tx.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
|
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) {
|
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,
|
// 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
|
// we need to take back some coins ... this is called "change". Add another output that sends the change
|
||||||
|
@ -58,18 +58,25 @@ public class WalletTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void basicSpending() throws Exception {
|
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);
|
BigInteger v1 = Utils.toNanoCoins(1, 0);
|
||||||
Transaction t1 = createFakeTx(params, v1, myAddress);
|
Transaction t1 = createFakeTx(params, v1, myAddress);
|
||||||
|
|
||||||
wallet.receiveFromBlock(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
|
wallet.receiveFromBlock(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||||
assertEquals(v1, wallet.getBalance());
|
assertEquals(v1, wallet.getBalance());
|
||||||
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
|
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
|
||||||
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL));
|
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);
|
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.UNSPENT));
|
||||||
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL));
|
assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL));
|
||||||
|
|
||||||
@ -77,6 +84,10 @@ public class WalletTest {
|
|||||||
assertEquals(1, t2.getInputs().size());
|
assertEquals(1, t2.getInputs().size());
|
||||||
assertEquals(myAddress, t2.getInputs().get(0).getScriptSig().getFromAddress());
|
assertEquals(myAddress, t2.getInputs().get(0).getScriptSig().getFromAddress());
|
||||||
assertEquals(t2.getConfidence().getConfidenceType(), TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN);
|
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!
|
// We have NOT proven that the signature is correct!
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ public class WalletTool {
|
|||||||
" --output=1GthXFQMktFLWdh5EPNGqbq3H6WdG8zsWj:1.245\n" +
|
" --output=1GthXFQMktFLWdh5EPNGqbq3H6WdG8zsWj:1.245\n" +
|
||||||
" If the output destination starts with 04 and is 65 bytes (130 chars) it will be\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" +
|
" 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" +
|
"\n>>> WAITING\n" +
|
||||||
"You can wait for the condition specified by the --waitfor flag to become true. Transactions and new\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();
|
parser.accepts("peers").withRequiredArg();
|
||||||
OptionSpec<String> outputFlag = parser.accepts("output").withRequiredArg();
|
OptionSpec<String> outputFlag = parser.accepts("output").withRequiredArg();
|
||||||
parser.accepts("value").withRequiredArg();
|
parser.accepts("value").withRequiredArg();
|
||||||
|
parser.accepts("fee").withRequiredArg();
|
||||||
conditionFlag = parser.accepts("condition").withRequiredArg();
|
conditionFlag = parser.accepts("condition").withRequiredArg();
|
||||||
options = parser.parse(args);
|
options = parser.parse(args);
|
||||||
|
|
||||||
@ -321,7 +322,11 @@ public class WalletTool {
|
|||||||
System.err.println("You must specify at least one --output=addr:value.");
|
System.err.println("You must specify at least one --output=addr:value.");
|
||||||
return;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +348,7 @@ public class WalletTool {
|
|||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void send(List<String> outputs) {
|
private static void send(List<String> outputs, BigInteger fee) {
|
||||||
try {
|
try {
|
||||||
// Convert the input strings to outputs.
|
// Convert the input strings to outputs.
|
||||||
Transaction t = new Transaction(params);
|
Transaction t = new Transaction(params);
|
||||||
@ -377,6 +382,7 @@ public class WalletTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Wallet.SendRequest req = Wallet.SendRequest.forTx(t);
|
Wallet.SendRequest req = Wallet.SendRequest.forTx(t);
|
||||||
|
req.fee = fee;
|
||||||
if (!wallet.completeTx(req)) {
|
if (!wallet.completeTx(req)) {
|
||||||
System.err.println("Insufficient funds: have " + wallet.getBalance());
|
System.err.println("Insufficient funds: have " + wallet.getBalance());
|
||||||
return;
|
return;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user