Skip to content

Commit

Permalink
Basic support for version 2 transactions.
Browse files Browse the repository at this point in the history
Rather than considering all version 2 transactions risky, we now look more closely if any of its
inputs has a relative lock time. We can't check the relative locks though, because we usually
don't have the spent outputs (to know when they were creted).
  • Loading branch information
Andreas Schildbach committed Feb 22, 2018
1 parent 6ba1b95 commit 9e7cb1c
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 2 deletions.
22 changes: 21 additions & 1 deletion core/src/main/java/org/bitcoinj/core/Transaction.java
Expand Up @@ -659,6 +659,9 @@ public String toString(@Nullable AbstractBlockChain chain) {
}
s.append('\n');
}
if (hasRelativeLockTime()) {
s.append(" has relative lock time\n");
}
if (isOptInFullRBF()) {
s.append(" opts into full replace-by-fee\n");
}
Expand Down Expand Up @@ -702,6 +705,8 @@ public String toString(@Nullable AbstractBlockChain chain) {
s.append("\n sequence:").append(Long.toHexString(in.getSequenceNumber()));
if (in.isOptInFullRBF())
s.append(", opts into full RBF");
if (version >=2 && in.hasRelativeLockTime())
s.append(", has RLT");
}
} catch (Exception e) {
s.append("[exception: ").append(e.getMessage()).append("]");
Expand Down Expand Up @@ -1287,7 +1292,8 @@ public void verify() throws VerificationException {
}

/**
* <p>A transaction is time locked if at least one of its inputs is non-final and it has a lock time</p>
* <p>A transaction is time-locked if at least one of its inputs is non-final and it has a lock time. A transaction can
* also have a relative lock time which this method doesn't tell. Use {@link #hasRelativeLockTime()} to find out.</p>
*
* <p>To check if this transaction is final at a given height and time, see {@link Transaction#isFinal(int, long)}
* </p>
Expand All @@ -1301,6 +1307,20 @@ public boolean isTimeLocked() {
return false;
}

/**
* A transaction has a relative lock time
* (<a href="https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki">BIP 68</a>) if it is version 2 or
* higher and at least one of its inputs has its {@link TransactionInput.SEQUENCE_LOCKTIME_DISABLE_FLAG} cleared.
*/
public boolean hasRelativeLockTime() {
if (version < 2)
return false;
for (TransactionInput input : getInputs())
if (input.hasRelativeLockTime())
return true;
return false;
}

/**
* Returns whether this transaction will opt into the
* <a href="https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki">full replace-by-fee </a> semantics.
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/java/org/bitcoinj/core/TransactionInput.java
Expand Up @@ -46,6 +46,11 @@
public class TransactionInput extends ChildMessage {
/** Magic sequence number that indicates there is no sequence number. */
public static final long NO_SEQUENCE = 0xFFFFFFFFL;
/**
* BIP68: If this flag set, sequence is NOT interpreted as a relative lock-time.
*/
public static final long SEQUENCE_LOCKTIME_DISABLE_FLAG = 1L << 31;

private static final byte[] EMPTY_ARRAY = new byte[0];
// Magic outpoint index that indicates the input is in fact unconnected.
private static final long UNCONNECTED = 0xFFFFFFFFL;
Expand Down Expand Up @@ -385,6 +390,14 @@ public boolean isOptInFullRBF() {
return sequence < NO_SEQUENCE - 1;
}

/**
* Returns whether this input, if it belongs to a version 2 (or higher) transaction, has
* <a href="https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki">relative lock-time</a> enabled.
*/
public boolean hasRelativeLockTime() {
return (sequence & SEQUENCE_LOCKTIME_DISABLE_FLAG) == 0;
}

/**
* For a connected transaction, runs the script against the connected pubkey and verifies they are correct.
* @throws ScriptException if the script did not verify.
Expand Down
Expand Up @@ -89,6 +89,13 @@ private Result analyzeIsFinal() {
return Result.NON_FINAL;
}

// Relative time-locked transactions are risky too. We can't check the locks because usually we don't know the
// spent outputs (to know when they were created).
if (tx.hasRelativeLockTime()) {
nonFinal = tx;
return Result.NON_FINAL;
}

if (wallet == null)
return null;

Expand Down Expand Up @@ -133,7 +140,7 @@ public enum RuleViolation {
*/
public static RuleViolation isStandard(Transaction tx) {
// TODO: Finish this function off.
if (tx.getVersion() > 1 || tx.getVersion() < 1) {
if (tx.getVersion() > 2 || tx.getVersion() < 1) {
log.warn("TX considered non-standard due to unknown version number {}", tx.getVersion());
return RuleViolation.VERSION;
}
Expand Down
Expand Up @@ -28,6 +28,7 @@

import java.util.*;

import static com.google.common.base.Preconditions.checkState;
import static org.bitcoinj.core.Coin.*;
import static org.bitcoinj.script.ScriptOpCodes.*;
import static org.junit.Assert.*;
Expand Down Expand Up @@ -231,4 +232,37 @@ public void optInFullRBF() throws Exception {
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
assertEquals(tx, analysis.getNonFinal());
}

@Test
public void relativeLockTime() throws Exception {
Transaction tx = FakeTxBuilder.createFakeTx(PARAMS);
tx.setVersion(2);
checkState(!tx.hasRelativeLockTime());

tx.getInput(0).setSequenceNumber(TransactionInput.NO_SEQUENCE);
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());

tx.getInput(0).setSequenceNumber(0);
analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
assertEquals(tx, analysis.getNonFinal());
}

@Test
public void transactionVersions() throws Exception {
Transaction tx = FakeTxBuilder.createFakeTx(PARAMS);
tx.setVersion(1);
DefaultRiskAnalysis analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());

tx.setVersion(2);
analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
assertEquals(RiskAnalysis.Result.OK, analysis.analyze());

tx.setVersion(3);
analysis = DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS);
assertEquals(RiskAnalysis.Result.NON_STANDARD, analysis.analyze());
assertEquals(tx, analysis.getNonStandard());
}
}

0 comments on commit 9e7cb1c

Please sign in to comment.