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 02b1e29b93
commit 30f9483ca3
3 changed files with 66 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;
@@ -636,6 +638,9 @@ public class Transaction extends ChildMessage implements Serializable {
}
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();
@@ -675,6 +680,10 @@ public class Transaction extends ChildMessage implements Serializable {
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("]");
}
@@ -1272,6 +1281,17 @@ public class Transaction extends ChildMessage implements Serializable {
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

@@ -22,6 +22,8 @@ import org.bitcoinj.wallet.DefaultRiskAnalysis;
import org.bitcoinj.wallet.KeyBag;
import org.bitcoinj.wallet.RedeemData;
import com.google.common.base.Joiner;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.ObjectOutputStream;
@@ -48,7 +50,7 @@ public class TransactionInput extends ChildMessage implements Serializable {
// 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;
@@ -273,18 +275,6 @@ public class TransactionInput extends ChildMessage implements Serializable {
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,
@@ -409,6 +399,14 @@ public class TransactionInput extends ChildMessage implements Serializable {
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.
@@ -492,4 +490,26 @@ public class TransactionInput extends ChildMessage implements Serializable {
result = 31 * result + (scriptSig != null ? scriptSig.hashCode() : 0);
return result;
}
/**
* 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

@@ -10,6 +10,8 @@ import org.junit.Test;
import org.easymock.EasyMock;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
@@ -296,4 +298,14 @@ public class TransactionTest {
tx.addSignedInput(fakeTx.getOutput(0).getOutPointFor(), script, key);
}
}
@Test
public void optInFullRBF() {
// a standard transaction as wallets would create
Transaction tx = newTransaction();
assertFalse(tx.isOptInFullRBF());
tx.getInputs().get(0).setSequenceNumber(TransactionInput.NO_SEQUENCE - 2);
assertTrue(tx.isOptInFullRBF());
}
}