Basic support for version 2 transactions.

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).
This commit is contained in:
Andreas Schildbach 2017-08-30 17:31:52 +02:00
parent 3061734993
commit 850f219607
4 changed files with 71 additions and 2 deletions

View File

@ -666,6 +666,9 @@ public class Transaction extends ChildMessage {
}
s.append('\n');
}
if (hasRelativeLockTime()) {
s.append(" has relative lock time\n");
}
if (isOptInFullRBF()) {
s.append(" opts into full replace-by-fee\n");
}
@ -710,6 +713,8 @@ public class Transaction extends ChildMessage {
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("]");
@ -1339,7 +1344,8 @@ public class Transaction extends ChildMessage {
}
/**
* <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>
@ -1353,6 +1359,20 @@ public class Transaction extends ChildMessage {
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.

View File

@ -415,6 +415,14 @@ public class TransactionInput extends ChildMessage {
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.

View File

@ -89,6 +89,13 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
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;
@ -133,7 +140,7 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
*/
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;
}

View File

@ -28,6 +28,7 @@ import org.junit.*;
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.*;
@ -231,4 +232,37 @@ public class DefaultRiskAnalysisTest {
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());
}
}