3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-13 10:45:51 +00:00

Be aware of opt-in full replace-by-fee.

This commit is contained in:
Andreas Schildbach 2015-11-29 12:18:37 +01:00
parent ee1aa05460
commit 786a11187e
3 changed files with 64 additions and 14 deletions

View File

@ -24,6 +24,8 @@ import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptOpCodes;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.wallet.WalletTransaction.Pool;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
@ -641,6 +643,9 @@ public class Transaction extends ChildMessage {
}
s.append(String.format(Locale.US, " time locked until %s%n", time));
}
if (isOptInFullRBF()) {
s.append(" opts into full replace-by-fee%n");
}
if (inputs.size() == 0) {
s.append(String.format(Locale.US, " INCOMPLETE: No inputs!%n"));
return s.toString();
@ -680,6 +685,10 @@ public class Transaction extends ChildMessage {
s.append(Utils.HEX.encode(scriptPubKey.getPubKeyHash()));
}
}
String flags = Joiner.on(", ").skipNulls().join(in.hasSequence() ? "has sequence" : null,
in.isOptInFullRBF() ? "opts into full RBF" : null);
if (!flags.isEmpty())
s.append("\n (").append(flags).append(')');
} catch (Exception e) {
s.append("[exception: ").append(e.getMessage()).append("]");
}
@ -1281,6 +1290,17 @@ public class Transaction extends ChildMessage {
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.
*/
public boolean isOptInFullRBF() {
for (TransactionInput input : getInputs())
if (input.isOptInFullRBF())
return true;
return false;
}
/**
* <p>Returns true if this transaction is considered finalized and can be placed in a block. Non-finalized
* transactions won't be included by miners and can be replaced with newer versions using sequence numbers.

View File

@ -21,6 +21,8 @@ import org.bitcoinj.script.Script;
import org.bitcoinj.wallet.DefaultRiskAnalysis;
import org.bitcoinj.wallet.KeyBag;
import org.bitcoinj.wallet.RedeemData;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import javax.annotation.Nullable;
@ -46,7 +48,7 @@ public class TransactionInput extends ChildMessage {
// Magic outpoint index that indicates the input is in fact unconnected.
private static final long UNCONNECTED = 0xFFFFFFFFL;
// Allows for altering transactions after they were broadcast.
// Allows for altering transactions after they were broadcast. Values below NO_SEQUENCE-1 mean it can be altered.
private long sequence;
// Data needed to connect to the output of the transaction we're gathering coins from.
private TransactionOutPoint outpoint;
@ -259,18 +261,6 @@ public class TransactionInput extends ChildMessage {
return value;
}
/**
* Returns a human readable debug string.
*/
@Override
public String toString() {
try {
return isCoinBase() ? "TxIn: COINBASE" : "TxIn for [" + outpoint + "]: " + getScriptSig();
} catch (ScriptException e) {
throw new RuntimeException(e);
}
}
public enum ConnectionResult {
NO_SUCH_TX,
ALREADY_SPENT,
@ -385,6 +375,14 @@ public class TransactionInput extends ChildMessage {
return sequence != NO_SEQUENCE;
}
/**
* Returns whether this input will cause a transaction to opt into the
* <a href="https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki">full replace-by-fee </a> semantics.
*/
public boolean isOptInFullRBF() {
return sequence < NO_SEQUENCE - 1;
}
/**
* For a connected transaction, runs the script against the connected pubkey and verifies they are correct.
* @throws ScriptException if the script did not verify.
@ -467,4 +465,26 @@ public class TransactionInput extends ChildMessage {
public int hashCode() {
return Objects.hashCode(sequence, outpoint, Arrays.hashCode(scriptBytes));
}
/**
* Returns a human readable debug string.
*/
@Override
public String toString() {
StringBuilder s = new StringBuilder("TxIn");
try {
if (isCoinBase()) {
s.append(": COINBASE");
} else {
s.append(" for [").append(outpoint).append("]: ").append(getScriptSig());
String flags = Joiner.on(", ").skipNulls().join(hasSequence() ? "has sequence" : null,
isOptInFullRBF() ? "opts into full RBF" : null);
if (!flags.isEmpty())
s.append(" (").append(flags).append(')');
}
return s.toString();
} catch (ScriptException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -403,4 +403,14 @@ public class TransactionTest {
final Transaction transaction = PARAMS.getDefaultSerializer().makeTransaction(transactionBytes);
transaction.checkCoinBaseHeight(height);
}
@Test
public void optInFullRBF() {
// a standard transaction as wallets would create
Transaction tx = FakeTxBuilder.createFakeTx(PARAMS);
assertFalse(tx.isOptInFullRBF());
tx.getInputs().get(0).setSequenceNumber(TransactionInput.NO_SEQUENCE - 2);
assertTrue(tx.isOptInFullRBF());
}
}