sequence number handling improvements

This commit is contained in:
Craig Raw 2020-04-05 16:01:24 +02:00
parent d28186f8c9
commit 130fea0937
4 changed files with 57 additions and 14 deletions

View file

@ -21,9 +21,10 @@ public class Transaction extends TransactionPart {
public static final int MAX_BLOCK_SIZE = 1000 * 1000; public static final int MAX_BLOCK_SIZE = 1000 * 1000;
public static final long MAX_BITCOIN = 21 * 1000 * 1000L; public static final long MAX_BITCOIN = 21 * 1000 * 1000L;
public static final long SATOSHIS_PER_BITCOIN = 100 * 1000 * 1000L; public static final long SATOSHIS_PER_BITCOIN = 100 * 1000 * 1000L;
public static final long MAX_BLOCK_LOCKTIME = 500000000L;
private long version; private long version;
private long lockTime; private long locktime;
private boolean segwit; private boolean segwit;
private int segwitVersion; private int segwitVersion;
@ -45,19 +46,34 @@ public class Transaction extends TransactionPart {
this.version = version; this.version = version;
} }
public long getLockTime() { public long getLocktime() {
return lockTime; return locktime;
} }
public void setLockTime(long lockTime) { public void setLocktime(long locktime) {
this.lockTime = lockTime; this.locktime = locktime;
} }
public boolean isLockTimeEnabled() { public boolean isLocktimeEnabled() {
if(lockTime == 0) return false; if(locktime == 0) return false;
return isLocktimeSequenceEnabled();
}
public boolean isLocktimeSequenceEnabled() {
for(TransactionInput input : inputs) {
if(!input.isAbsoluteTimeLockDisabled()) {
return true;
}
}
return false;
}
public boolean isReplaceByFee() {
if(locktime == 0) return false;
for(TransactionInput input : inputs) { for(TransactionInput input : inputs) {
if(input.getSequenceNumber() != TransactionInput.SEQUENCE_LOCKTIME_DISABLED) { if(input.isReplaceByFeeEnabled()) {
return true; return true;
} }
} }
@ -149,7 +165,7 @@ public class Transaction extends TransactionPart {
} }
} }
// lock_time // lock_time
uint32ToByteStreamLE(lockTime, stream); uint32ToByteStreamLE(locktime, stream);
} }
/** /**
@ -176,7 +192,7 @@ public class Transaction extends TransactionPart {
if (segwit) if (segwit)
parseWitnesses(); parseWitnesses();
// lock_time // lock_time
lockTime = readUint32(); locktime = readUint32();
length = cursor - offset; length = cursor - offset;
} }
@ -456,7 +472,7 @@ public class Transaction extends TransactionPart {
uint64ToByteStreamLE(BigInteger.valueOf(prevValue), bos); uint64ToByteStreamLE(BigInteger.valueOf(prevValue), bos);
uint32ToByteStreamLE(inputs.get(inputIndex).getSequenceNumber(), bos); uint32ToByteStreamLE(inputs.get(inputIndex).getSequenceNumber(), bos);
bos.write(hashOutputs); bos.write(hashOutputs);
uint32ToByteStreamLE(this.lockTime, bos); uint32ToByteStreamLE(this.locktime, bos);
uint32ToByteStreamLE(0x000000ff & sigHashType, bos); uint32ToByteStreamLE(0x000000ff & sigHashType, bos);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); // Cannot happen. throw new RuntimeException(e); // Cannot happen.

View file

@ -6,7 +6,10 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
public class TransactionInput extends TransactionPart { public class TransactionInput extends TransactionPart {
public static final long SEQUENCE_LOCKTIME_DISABLED = 0xFFFFFFFF; public static final long SEQUENCE_LOCKTIME_DISABLED = 4294967295L;
public static final long SEQUENCE_RBF_ENABLED = 4294967293L;
public static final long MAX_RELATIVE_TIMELOCK = 0x40FFFF;
public static final long MAX_RELATIVE_TIMELOCK_IN_BLOCKS = 0xFFFF;
// Allows for altering transactions after they were broadcast. Values below NO_SEQUENCE-1 mean it can be altered. // Allows for altering transactions after they were broadcast. Values below NO_SEQUENCE-1 mean it can be altered.
private long sequence; private long sequence;
@ -97,6 +100,30 @@ public class TransactionInput extends TransactionPart {
(outpoint.getIndex() & 0xFFFFFFFFL) == 0xFFFFFFFFL; // -1 but all is serialized to the wire as unsigned int. (outpoint.getIndex() & 0xFFFFFFFFL) == 0xFFFFFFFFL; // -1 but all is serialized to the wire as unsigned int.
} }
public boolean isReplaceByFeeEnabled() {
return sequence <= SEQUENCE_RBF_ENABLED;
}
public boolean isAbsoluteTimeLockDisabled() {
return sequence >= SEQUENCE_LOCKTIME_DISABLED;
}
public boolean isAbsoluteTimeLocked() {
return !isAbsoluteTimeLockDisabled() && !isRelativeTimeLocked();
}
public boolean isRelativeTimeLocked() {
return sequence <= MAX_RELATIVE_TIMELOCK;
}
public boolean isRelativeTimeLockedInBlocks() {
return sequence <= MAX_RELATIVE_TIMELOCK_IN_BLOCKS;
}
public long getRelativeLocktime() {
return sequence & MAX_RELATIVE_TIMELOCK_IN_BLOCKS;
}
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException { protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
outpoint.bitcoinSerializeToStream(stream); outpoint.bitcoinSerializeToStream(stream);
stream.write(new VarInt(scriptBytes.length).encode()); stream.write(new VarInt(scriptBytes.length).encode());

View file

@ -202,7 +202,7 @@ public class PSBT {
transaction.verify(); transaction.verify();
inputs = transaction.getInputs().size(); inputs = transaction.getInputs().size();
outputs = transaction.getOutputs().size(); outputs = transaction.getOutputs().size();
log.debug("Transaction with txid: " + transaction.getTxId() + " version " + transaction.getVersion() + " size " + transaction.getMessageSize() + " locktime " + transaction.getLockTime()); log.debug("Transaction with txid: " + transaction.getTxId() + " version " + transaction.getVersion() + " size " + transaction.getMessageSize() + " locktime " + transaction.getLocktime());
for(TransactionInput input: transaction.getInputs()) { for(TransactionInput input: transaction.getInputs()) {
if(input.getScriptSig().getProgram().length != 0) { if(input.getScriptSig().getProgram().length != 0) {
throw new PSBTParseException("Unsigned tx input does not have empty scriptSig"); throw new PSBTParseException("Unsigned tx input does not have empty scriptSig");

View file

@ -65,7 +65,7 @@ public class PSBTInput {
} }
this.nonWitnessUtxo = nonWitnessTx; this.nonWitnessUtxo = nonWitnessTx;
log.debug("Found input non witness utxo with txid: " + nonWitnessTx.getTxId() + " version " + nonWitnessTx.getVersion() + " size " + nonWitnessTx.getMessageSize() + " locktime " + nonWitnessTx.getLockTime()); log.debug("Found input non witness utxo with txid: " + nonWitnessTx.getTxId() + " version " + nonWitnessTx.getVersion() + " size " + nonWitnessTx.getMessageSize() + " locktime " + nonWitnessTx.getLocktime());
for(TransactionInput input: nonWitnessTx.getInputs()) { for(TransactionInput input: nonWitnessTx.getInputs()) {
log.debug(" Transaction input references txid: " + input.getOutpoint().getHash() + " vout " + input.getOutpoint().getIndex() + " with script " + input.getScriptSig()); log.debug(" Transaction input references txid: " + input.getOutpoint().getHash() + " vout " + input.getOutpoint().getIndex() + " with script " + input.getScriptSig());
} }