diff --git a/src/main/java/com/sparrowwallet/drongo/protocol/Transaction.java b/src/main/java/com/sparrowwallet/drongo/protocol/Transaction.java index 7445aed..d999d86 100644 --- a/src/main/java/com/sparrowwallet/drongo/protocol/Transaction.java +++ b/src/main/java/com/sparrowwallet/drongo/protocol/Transaction.java @@ -21,9 +21,10 @@ public class Transaction extends TransactionPart { public static final int MAX_BLOCK_SIZE = 1000 * 1000; public static final long MAX_BITCOIN = 21 * 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 lockTime; + private long locktime; private boolean segwit; private int segwitVersion; @@ -45,19 +46,34 @@ public class Transaction extends TransactionPart { this.version = version; } - public long getLockTime() { - return lockTime; + public long getLocktime() { + return locktime; } - public void setLockTime(long lockTime) { - this.lockTime = lockTime; + public void setLocktime(long locktime) { + this.locktime = locktime; } - public boolean isLockTimeEnabled() { - if(lockTime == 0) return false; + public boolean isLocktimeEnabled() { + 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) { - if(input.getSequenceNumber() != TransactionInput.SEQUENCE_LOCKTIME_DISABLED) { + if(input.isReplaceByFeeEnabled()) { return true; } } @@ -149,7 +165,7 @@ public class Transaction extends TransactionPart { } } // lock_time - uint32ToByteStreamLE(lockTime, stream); + uint32ToByteStreamLE(locktime, stream); } /** @@ -176,7 +192,7 @@ public class Transaction extends TransactionPart { if (segwit) parseWitnesses(); // lock_time - lockTime = readUint32(); + locktime = readUint32(); length = cursor - offset; } @@ -456,7 +472,7 @@ public class Transaction extends TransactionPart { uint64ToByteStreamLE(BigInteger.valueOf(prevValue), bos); uint32ToByteStreamLE(inputs.get(inputIndex).getSequenceNumber(), bos); bos.write(hashOutputs); - uint32ToByteStreamLE(this.lockTime, bos); + uint32ToByteStreamLE(this.locktime, bos); uint32ToByteStreamLE(0x000000ff & sigHashType, bos); } catch (IOException e) { throw new RuntimeException(e); // Cannot happen. diff --git a/src/main/java/com/sparrowwallet/drongo/protocol/TransactionInput.java b/src/main/java/com/sparrowwallet/drongo/protocol/TransactionInput.java index 040c0b1..9c3f88a 100644 --- a/src/main/java/com/sparrowwallet/drongo/protocol/TransactionInput.java +++ b/src/main/java/com/sparrowwallet/drongo/protocol/TransactionInput.java @@ -6,7 +6,10 @@ import java.io.IOException; import java.io.OutputStream; 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. 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. } + 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 { outpoint.bitcoinSerializeToStream(stream); stream.write(new VarInt(scriptBytes.length).encode()); diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java index d40bd7d..d207127 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java @@ -202,7 +202,7 @@ public class PSBT { transaction.verify(); inputs = transaction.getInputs().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()) { if(input.getScriptSig().getProgram().length != 0) { throw new PSBTParseException("Unsigned tx input does not have empty scriptSig"); diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java index 42a484b..e450df3 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java @@ -65,7 +65,7 @@ public class PSBTInput { } 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()) { log.debug(" Transaction input references txid: " + input.getOutpoint().getHash() + " vout " + input.getOutpoint().getIndex() + " with script " + input.getScriptSig()); }