mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-30 23:02:15 +00:00
Better support for lock timed transactions.
Lock times are now included in various toString dumps. Transactions can estimate their lock time when the time is specified as a block number. Add support to WalletTool for creating timelocked transactions.
This commit is contained in:
parent
48cdc1d9e7
commit
b5b43f3a15
@ -774,4 +774,19 @@ public abstract class AbstractBlockChain {
|
||||
public synchronized boolean isOrphan(Sha256Hash block) {
|
||||
return orphanBlocks.containsKey(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an estimate of when the given block will be reached, assuming a perfect 10 minute average for each
|
||||
* block. This is useful for turning transaction lock times into human readable times. Note that a height in
|
||||
* the past will still be estimated, even though the time of solving is actually known (we won't scan backwards
|
||||
* through the chain to obtain the right answer).
|
||||
*/
|
||||
public Date estimateBlockTime(int height) {
|
||||
synchronized (chainHeadLock) {
|
||||
long offset = height - chainHead.getHeight();
|
||||
long headTime = chainHead.getHeader().getTimeSeconds();
|
||||
long estimated = (headTime * 1000) + (1000L * 60L * 10L * offset);
|
||||
return new Date(estimated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
import static com.google.bitcoin.core.Utils.*;
|
||||
@ -51,12 +53,8 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
// These are serialized in both bitcoin and java serialization.
|
||||
private long version;
|
||||
private ArrayList<TransactionInput> inputs;
|
||||
//a cached copy to prevent constantly rewrapping
|
||||
//private transient List<TransactionInput> immutableInputs;
|
||||
|
||||
private ArrayList<TransactionOutput> outputs;
|
||||
//a cached copy to prevent constantly rewrapping
|
||||
//private transient List<TransactionOutput> immutableOutputs;
|
||||
|
||||
private long lockTime;
|
||||
|
||||
@ -548,13 +546,31 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
return getConfidence().getDepthInBlocks() >= params.getSpendableCoinbaseDepth();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return toString(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* A human readable version of the transaction useful for debugging. The format is not guaranteed to be stable.
|
||||
* @param chain If provided, will be used to estimate lock times (if set). Can be null.
|
||||
*/
|
||||
public String toString() {
|
||||
public String toString(AbstractBlockChain chain) {
|
||||
// Basic info about the tx.
|
||||
StringBuffer s = new StringBuffer();
|
||||
s.append(String.format(" %s: %s%n", getHashAsString(), getConfidence()));
|
||||
if (lockTime > 0) {
|
||||
String time;
|
||||
if (lockTime < LOCKTIME_THRESHOLD) {
|
||||
time = "block " + lockTime;
|
||||
if (chain != null) {
|
||||
time = time + " (estimated to be reached at " +
|
||||
chain.estimateBlockTime((int)lockTime).toString() + ")";
|
||||
}
|
||||
} else {
|
||||
time = new Date(lockTime).toString();
|
||||
}
|
||||
s.append(String.format(" time locked until %s%n", time));
|
||||
}
|
||||
if (inputs.size() == 0) {
|
||||
s.append(String.format(" INCOMPLETE: No inputs!%n"));
|
||||
return s.toString();
|
||||
@ -1026,4 +1042,28 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string either as a whole number of blocks, or if it contains slashes as a YYYY/MM/DD format date
|
||||
* and returns the lock time in wire format.
|
||||
*/
|
||||
public static long parseLockTimeStr(String lockTimeStr) throws ParseException {
|
||||
if (lockTimeStr.indexOf("/") != -1) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
|
||||
Date date = format.parse(lockTimeStr);
|
||||
return date.getTime() / 1000;
|
||||
}
|
||||
return Long.parseLong(lockTimeStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns either the lock time as a date, if it was specified in seconds, or an estimate based on the time in
|
||||
* the current head block if it was specified as a block time.
|
||||
*/
|
||||
public Date estimateLockTime(AbstractBlockChain chain) {
|
||||
if (lockTime < LOCKTIME_THRESHOLD)
|
||||
return chain.estimateBlockTime((int)getLockTime());
|
||||
else
|
||||
return new Date(getLockTime()*1000);
|
||||
}
|
||||
}
|
||||
|
@ -1719,10 +1719,17 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
|
||||
@Override
|
||||
public synchronized String toString() {
|
||||
return toString(false);
|
||||
return toString(false, null);
|
||||
}
|
||||
|
||||
public synchronized String toString(boolean includePrivateKeys) {
|
||||
/**
|
||||
* Formats the wallet as a human readable piece of text. Intended for debugging, the format is not meant to be
|
||||
* stable or human readable.
|
||||
* @param includePrivateKeys Whether raw private key data should be included.
|
||||
* @param chain If set, will be used to estimate lock times for block timelocked transactions.
|
||||
* @return
|
||||
*/
|
||||
public synchronized String toString(boolean includePrivateKeys, AbstractBlockChain chain) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(String.format("Wallet containing %s BTC in:%n", bitcoinValueToFriendlyString(getBalance())));
|
||||
builder.append(String.format(" %d unspent transactions%n", unspent.size()));
|
||||
@ -1743,28 +1750,29 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
// Print the transactions themselves
|
||||
if (unspent.size() > 0) {
|
||||
builder.append("\nUNSPENT:\n");
|
||||
toStringHelper(builder, unspent);
|
||||
toStringHelper(builder, unspent, chain);
|
||||
}
|
||||
if (spent.size() > 0) {
|
||||
builder.append("\nSPENT:\n");
|
||||
toStringHelper(builder, spent);
|
||||
toStringHelper(builder, spent, chain);
|
||||
}
|
||||
if (pending.size() > 0) {
|
||||
builder.append("\nPENDING:\n");
|
||||
toStringHelper(builder, pending);
|
||||
toStringHelper(builder, pending, chain);
|
||||
}
|
||||
if (inactive.size() > 0) {
|
||||
builder.append("\nINACTIVE:\n");
|
||||
toStringHelper(builder, inactive);
|
||||
toStringHelper(builder, inactive, chain);
|
||||
}
|
||||
if (dead.size() > 0) {
|
||||
builder.append("\nDEAD:\n");
|
||||
toStringHelper(builder, dead);
|
||||
toStringHelper(builder, dead, chain);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void toStringHelper(StringBuilder builder, Map<Sha256Hash, Transaction> transactionMap) {
|
||||
private void toStringHelper(StringBuilder builder, Map<Sha256Hash, Transaction> transactionMap,
|
||||
AbstractBlockChain chain) {
|
||||
for (Transaction tx : transactionMap.values()) {
|
||||
try {
|
||||
builder.append("Sends ");
|
||||
@ -1777,7 +1785,7 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
} catch (ScriptException e) {
|
||||
// Ignore and don't print this line.
|
||||
}
|
||||
builder.append(tx);
|
||||
builder.append(tx.toString(chain));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,8 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import static com.google.bitcoin.core.TestUtils.createFakeBlock;
|
||||
import static com.google.bitcoin.core.TestUtils.createFakeTx;
|
||||
@ -360,4 +362,13 @@ public class BlockChainTest {
|
||||
b1.verifyHeader();
|
||||
return b1;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void estimatedBlockTime() throws Exception {
|
||||
NetworkParameters params = NetworkParameters.prodNet();
|
||||
BlockChain prod = new BlockChain(params, new MemoryBlockStore(params));
|
||||
Date d = prod.estimateBlockTime(200000);
|
||||
// The actual date of block 200,000 was 2012-09-22 10:47:00
|
||||
assertEquals(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2012/10/23 17:35:05"), d);
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@ -86,9 +87,13 @@ public class WalletTool {
|
||||
" --action=SEND Creates a transaction with the given --output from this wallet and broadcasts, eg:\n" +
|
||||
" --output=1GthXFQMktFLWdh5EPNGqbq3H6WdG8zsWj:1.245\n" +
|
||||
" You can repeat --output=address:value multiple times.\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 or 33 bytes long it will be\n" +
|
||||
" treated as a public key instead of an address and the send will use \n" +
|
||||
" <key> CHECKSIG as the script. You can also specify a --fee=0.01\n" +
|
||||
" <key> CHECKSIG as the script.\n" +
|
||||
" Other options include:\n" +
|
||||
" --fee=0.01 sets the tx fee\n" +
|
||||
" --locktime=1234 sets the lock time to block 1234\n" +
|
||||
" --locktime=2013/01/01 sets the lock time to 1st Jan 2013\n" +
|
||||
|
||||
"\n>>> WAITING\n" +
|
||||
"You can wait for the condition specified by the --waitfor flag to become true. Transactions and new\n" +
|
||||
@ -242,6 +247,7 @@ public class WalletTool {
|
||||
parser.accepts("value").withRequiredArg();
|
||||
parser.accepts("fee").withRequiredArg();
|
||||
conditionFlag = parser.accepts("condition").withRequiredArg();
|
||||
parser.accepts("locktime").withRequiredArg();
|
||||
options = parser.parse(args);
|
||||
|
||||
if (args.length == 0 || options.has("help") || options.nonOptionArguments().size() > 0) {
|
||||
@ -348,7 +354,11 @@ public class WalletTool {
|
||||
if (options.has("fee")) {
|
||||
fee = Utils.toNanoCoins((String)options.valueOf("fee"));
|
||||
}
|
||||
send(outputFlag.values(options), fee);
|
||||
String lockTime = null;
|
||||
if (options.has("locktime")) {
|
||||
lockTime = (String) options.valueOf("locktime");
|
||||
}
|
||||
send(outputFlag.values(options), fee, lockTime);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -360,7 +370,15 @@ public class WalletTool {
|
||||
saveWallet(walletFile);
|
||||
|
||||
if (options.has(waitForFlag)) {
|
||||
wait(waitForFlag.value(options));
|
||||
WaitForEnum value;
|
||||
try {
|
||||
value = waitForFlag.value(options);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Could not understand the --waitfor flag: Valid options are WALLET_TX, BLOCK, " +
|
||||
"BALANCE and EVER");
|
||||
return;
|
||||
}
|
||||
wait(value);
|
||||
if (!wallet.isConsistent()) {
|
||||
System.err.println("************** WALLET IS INCONSISTENT *****************");
|
||||
return;
|
||||
@ -370,7 +388,7 @@ public class WalletTool {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
private static void send(List<String> outputs, BigInteger fee) {
|
||||
private static void send(List<String> outputs, BigInteger fee, String lockTimeStr) {
|
||||
try {
|
||||
// Convert the input strings to outputs.
|
||||
Transaction t = new Transaction(params);
|
||||
@ -383,7 +401,7 @@ public class WalletTool {
|
||||
String destination = parts[0];
|
||||
try {
|
||||
BigInteger value = Utils.toNanoCoins(parts[1]);
|
||||
if (destination.startsWith("04") && destination.length() == 130) {
|
||||
if (destination.startsWith("04") && (destination.length() == 130 || destination.length() == 66)) {
|
||||
// Treat as a raw public key.
|
||||
BigInteger pubKey = new BigInteger(destination, 16);
|
||||
ECKey key = new ECKey(null, pubKey);
|
||||
@ -409,6 +427,16 @@ public class WalletTool {
|
||||
System.err.println("Insufficient funds: have " + wallet.getBalance());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (lockTimeStr != null) {
|
||||
t.setLockTime(Transaction.parseLockTimeStr(lockTimeStr));
|
||||
// For lock times to take effect, at least one output must have a non-final sequence number.
|
||||
t.getInputs().get(0).setSequenceNumber(0);
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
System.err.println("Could not understand --locktime of " + lockTimeStr);
|
||||
return;
|
||||
}
|
||||
t = req.tx; // Not strictly required today.
|
||||
setup();
|
||||
peers.startAndWait();
|
||||
@ -661,7 +689,8 @@ public class WalletTool {
|
||||
wallet.keychain.remove(key);
|
||||
}
|
||||
|
||||
private static void dumpWallet() {
|
||||
System.out.println(wallet.toString(true));
|
||||
private static void dumpWallet() throws BlockStoreException {
|
||||
setup(); // To get the chain height so we can estimate lock times.
|
||||
System.out.println(wallet.toString(true, chain));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user