mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-03 05:57:21 +00:00
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:
@@ -659,6 +659,9 @@ public class Transaction extends ChildMessage {
|
|||||||
}
|
}
|
||||||
s.append('\n');
|
s.append('\n');
|
||||||
}
|
}
|
||||||
|
if (hasRelativeLockTime()) {
|
||||||
|
s.append(" has relative lock time\n");
|
||||||
|
}
|
||||||
if (isOptInFullRBF()) {
|
if (isOptInFullRBF()) {
|
||||||
s.append(" opts into full replace-by-fee\n");
|
s.append(" opts into full replace-by-fee\n");
|
||||||
}
|
}
|
||||||
@@ -702,6 +705,8 @@ public class Transaction extends ChildMessage {
|
|||||||
s.append("\n sequence:").append(Long.toHexString(in.getSequenceNumber()));
|
s.append("\n sequence:").append(Long.toHexString(in.getSequenceNumber()));
|
||||||
if (in.isOptInFullRBF())
|
if (in.isOptInFullRBF())
|
||||||
s.append(", opts into full RBF");
|
s.append(", opts into full RBF");
|
||||||
|
if (version >=2 && in.hasRelativeLockTime())
|
||||||
|
s.append(", has RLT");
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
s.append("[exception: ").append(e.getMessage()).append("]");
|
s.append("[exception: ").append(e.getMessage()).append("]");
|
||||||
@@ -1287,7 +1292,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>To check if this transaction is final at a given height and time, see {@link Transaction#isFinal(int, long)}
|
||||||
* </p>
|
* </p>
|
||||||
@@ -1301,6 +1307,20 @@ public class Transaction extends ChildMessage {
|
|||||||
return false;
|
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
|
* 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.
|
* <a href="https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki">full replace-by-fee </a> semantics.
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||||||
public class TransactionInput extends ChildMessage {
|
public class TransactionInput extends ChildMessage {
|
||||||
/** Magic sequence number that indicates there is no sequence number. */
|
/** Magic sequence number that indicates there is no sequence number. */
|
||||||
public static final long NO_SEQUENCE = 0xFFFFFFFFL;
|
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];
|
private static final byte[] EMPTY_ARRAY = new byte[0];
|
||||||
// Magic outpoint index that indicates the input is in fact unconnected.
|
// Magic outpoint index that indicates the input is in fact unconnected.
|
||||||
private static final long UNCONNECTED = 0xFFFFFFFFL;
|
private static final long UNCONNECTED = 0xFFFFFFFFL;
|
||||||
@@ -385,6 +390,14 @@ public class TransactionInput extends ChildMessage {
|
|||||||
return sequence < NO_SEQUENCE - 1;
|
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.
|
* For a connected transaction, runs the script against the connected pubkey and verifies they are correct.
|
||||||
* @throws ScriptException if the script did not verify.
|
* @throws ScriptException if the script did not verify.
|
||||||
|
|||||||
@@ -89,6 +89,13 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
|
|||||||
return Result.NON_FINAL;
|
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)
|
if (wallet == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -133,7 +140,7 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
|
|||||||
*/
|
*/
|
||||||
public static RuleViolation isStandard(Transaction tx) {
|
public static RuleViolation isStandard(Transaction tx) {
|
||||||
// TODO: Finish this function off.
|
// 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());
|
log.warn("TX considered non-standard due to unknown version number {}", tx.getVersion());
|
||||||
return RuleViolation.VERSION;
|
return RuleViolation.VERSION;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.junit.*;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static org.bitcoinj.core.Coin.*;
|
import static org.bitcoinj.core.Coin.*;
|
||||||
import static org.bitcoinj.script.ScriptOpCodes.*;
|
import static org.bitcoinj.script.ScriptOpCodes.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
@@ -231,4 +232,37 @@ public class DefaultRiskAnalysisTest {
|
|||||||
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
|
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
|
||||||
assertEquals(tx, analysis.getNonFinal());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user