Browse Source

Merge #8381: Make witness v0 outputs non-standard

c59c434 qa: Add test for standardness of segwit v0 outputs (Suhas Daftuar)
1ffaff2 Make witness v0 outputs non-standard before segwit activation (Johnson Lau)
0.14
Wladimir J. van der Laan 9 years ago
parent
commit
4b1a4d8810
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
  1. 78
      qa/rpc-tests/p2p-segwit.py
  2. 5
      src/main.cpp
  3. 9
      src/policy/policy.cpp
  4. 4
      src/policy/policy.h

78
qa/rpc-tests/p2p-segwit.py

@ -1086,6 +1086,82 @@ class SegWitTest(BitcoinTestFramework):
self.old_node.announce_tx_and_wait_for_getdata(block4.vtx[0]) self.old_node.announce_tx_and_wait_for_getdata(block4.vtx[0])
assert(block4.sha256 not in self.old_node.getdataset) assert(block4.sha256 not in self.old_node.getdataset)
# V0 segwit outputs should be standard after activation, but not before.
def test_standardness_v0(self, segwit_activated):
print("\tTesting standardness of v0 outputs (%s activation)" % ("after" if segwit_activated else "before"))
assert(len(self.utxo))
witness_program = CScript([OP_TRUE])
witness_hash = sha256(witness_program)
scriptPubKey = CScript([OP_0, witness_hash])
p2sh_pubkey = hash160(witness_program)
p2sh_scriptPubKey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL])
# First prepare a p2sh output (so that spending it will pass standardness)
p2sh_tx = CTransaction()
p2sh_tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")]
p2sh_tx.vout = [CTxOut(self.utxo[0].nValue-1000, p2sh_scriptPubKey)]
p2sh_tx.rehash()
# Mine it on test_node to create the confirmed output.
self.test_node.test_transaction_acceptance(p2sh_tx, with_witness=True, accepted=True)
self.nodes[0].generate(1)
sync_blocks(self.nodes)
# Now test standardness of v0 P2WSH outputs.
# Start by creating a transaction with two outputs.
tx = CTransaction()
tx.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_program]))]
tx.vout = [CTxOut(p2sh_tx.vout[0].nValue-10000, scriptPubKey)]
tx.vout.append(CTxOut(8000, scriptPubKey)) # Might burn this later
tx.rehash()
self.std_node.test_transaction_acceptance(tx, with_witness=True, accepted=segwit_activated)
# Now create something that looks like a P2PKH output. This won't be spendable.
scriptPubKey = CScript([OP_0, hash160(witness_hash)])
tx2 = CTransaction()
if segwit_activated:
# if tx was accepted, then we spend the second output.
tx2.vin = [CTxIn(COutPoint(tx.sha256, 1), b"")]
tx2.vout = [CTxOut(7000, scriptPubKey)]
tx2.wit.vtxinwit.append(CTxInWitness())
tx2.wit.vtxinwit[0].scriptWitness.stack = [witness_program]
else:
# if tx wasn't accepted, we just re-spend the p2sh output we started with.
tx2.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_program]))]
tx2.vout = [CTxOut(p2sh_tx.vout[0].nValue-1000, scriptPubKey)]
tx2.rehash()
self.std_node.test_transaction_acceptance(tx2, with_witness=True, accepted=segwit_activated)
# Now update self.utxo for later tests.
tx3 = CTransaction()
if segwit_activated:
# tx and tx2 were both accepted. Don't bother trying to reclaim the
# P2PKH output; just send tx's first output back to an anyone-can-spend.
sync_mempools(self.nodes)
tx3.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")]
tx3.vout = [CTxOut(tx.vout[0].nValue-1000, CScript([OP_TRUE]))]
tx3.wit.vtxinwit.append(CTxInWitness())
tx3.wit.vtxinwit[0].scriptWitness.stack = [witness_program]
tx3.rehash()
self.test_node.test_transaction_acceptance(tx3, with_witness=True, accepted=True)
else:
# tx and tx2 didn't go anywhere; just clean up the p2sh_tx output.
tx3.vin = [CTxIn(COutPoint(p2sh_tx.sha256, 0), CScript([witness_program]))]
tx3.vout = [CTxOut(p2sh_tx.vout[0].nValue-1000, witness_program)]
tx3.rehash()
self.test_node.test_transaction_acceptance(tx3, with_witness=True, accepted=True)
self.nodes[0].generate(1)
sync_blocks(self.nodes)
self.utxo.pop(0)
self.utxo.append(UTXO(tx3.sha256, 0, tx3.vout[0].nValue))
assert_equal(len(self.nodes[1].getrawmempool()), 0)
# Verify that future segwit upgraded transactions are non-standard, # Verify that future segwit upgraded transactions are non-standard,
# but valid in blocks. Can run this before and after segwit activation. # but valid in blocks. Can run this before and after segwit activation.
def test_segwit_versions(self): def test_segwit_versions(self):
@ -1658,6 +1734,7 @@ class SegWitTest(BitcoinTestFramework):
self.test_witness_tx_relay_before_segwit_activation() self.test_witness_tx_relay_before_segwit_activation()
self.test_block_relay(segwit_activated=False) self.test_block_relay(segwit_activated=False)
self.test_p2sh_witness(segwit_activated=False) self.test_p2sh_witness(segwit_activated=False)
self.test_standardness_v0(segwit_activated=False)
sync_blocks(self.nodes) sync_blocks(self.nodes)
@ -1679,6 +1756,7 @@ class SegWitTest(BitcoinTestFramework):
self.test_witness_input_length() self.test_witness_input_length()
self.test_block_relay(segwit_activated=True) self.test_block_relay(segwit_activated=True)
self.test_tx_relay_after_segwit_activation() self.test_tx_relay_after_segwit_activation()
self.test_standardness_v0(segwit_activated=True)
self.test_segwit_versions() self.test_segwit_versions()
self.test_premature_coinbase_witness_spend() self.test_premature_coinbase_witness_spend()
self.test_signature_version_1() self.test_signature_version_1()

5
src/main.cpp

@ -1143,13 +1143,14 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
} }
// Reject transactions with witness before segregated witness activates (override with -prematurewitness) // Reject transactions with witness before segregated witness activates (override with -prematurewitness)
if (!GetBoolArg("-prematurewitness",false) && !tx.wit.IsNull() && !IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus())) { bool witnessEnabled = IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus());
if (!GetBoolArg("-prematurewitness",false) && !tx.wit.IsNull() && !witnessEnabled) {
return state.DoS(0, false, REJECT_NONSTANDARD, "no-witness-yet", true); return state.DoS(0, false, REJECT_NONSTANDARD, "no-witness-yet", true);
} }
// Rather not work on nonstandard transactions (unless -testnet/-regtest) // Rather not work on nonstandard transactions (unless -testnet/-regtest)
string reason; string reason;
if (fRequireStandard && !IsStandardTx(tx, reason)) if (fRequireStandard && !IsStandardTx(tx, reason, witnessEnabled))
return state.DoS(0, false, REJECT_NONSTANDARD, reason); return state.DoS(0, false, REJECT_NONSTANDARD, reason);
// Only accept nLockTime-using transactions that can be mined in the next // Only accept nLockTime-using transactions that can be mined in the next

9
src/policy/policy.cpp

@ -31,7 +31,7 @@
* DUP CHECKSIG DROP ... repeated 100 times... OP_1 * DUP CHECKSIG DROP ... repeated 100 times... OP_1
*/ */
bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType) bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool witnessEnabled)
{ {
std::vector<std::vector<unsigned char> > vSolutions; std::vector<std::vector<unsigned char> > vSolutions;
if (!Solver(scriptPubKey, whichType, vSolutions)) if (!Solver(scriptPubKey, whichType, vSolutions))
@ -50,10 +50,13 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType)
(!fAcceptDatacarrier || scriptPubKey.size() > nMaxDatacarrierBytes)) (!fAcceptDatacarrier || scriptPubKey.size() > nMaxDatacarrierBytes))
return false; return false;
else if (!witnessEnabled && (whichType == TX_WITNESS_V0_KEYHASH || whichType == TX_WITNESS_V0_SCRIPTHASH))
return false;
return whichType != TX_NONSTANDARD; return whichType != TX_NONSTANDARD;
} }
bool IsStandardTx(const CTransaction& tx, std::string& reason) bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled)
{ {
if (tx.nVersion > CTransaction::MAX_STANDARD_VERSION || tx.nVersion < 1) { if (tx.nVersion > CTransaction::MAX_STANDARD_VERSION || tx.nVersion < 1) {
reason = "version"; reason = "version";
@ -92,7 +95,7 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason)
unsigned int nDataOut = 0; unsigned int nDataOut = 0;
txnouttype whichType; txnouttype whichType;
BOOST_FOREACH(const CTxOut& txout, tx.vout) { BOOST_FOREACH(const CTxOut& txout, tx.vout) {
if (!::IsStandard(txout.scriptPubKey, whichType)) { if (!::IsStandard(txout.scriptPubKey, whichType, witnessEnabled)) {
reason = "scriptpubkey"; reason = "scriptpubkey";
return false; return false;
} }

4
src/policy/policy.h

@ -55,12 +55,12 @@ static const unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_
static const unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE | static const unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS = LOCKTIME_VERIFY_SEQUENCE |
LOCKTIME_MEDIAN_TIME_PAST; LOCKTIME_MEDIAN_TIME_PAST;
bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType); bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType, const bool witnessEnabled = false);
/** /**
* Check for standard transaction types * Check for standard transaction types
* @return True if all outputs (scriptPubKeys) use only standard transaction forms * @return True if all outputs (scriptPubKeys) use only standard transaction forms
*/ */
bool IsStandardTx(const CTransaction& tx, std::string& reason); bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled = false);
/** /**
* Check for standard transaction types * Check for standard transaction types
* @param[in] mapInputs Map of previous transactions that have outputs we're spending * @param[in] mapInputs Map of previous transactions that have outputs we're spending

Loading…
Cancel
Save