Merge #8312: Fix mempool DoS vulnerability from malleated transactions
46c9620
Test that unnecessary witnesses can't be used for mempool DoS (Suhas Daftuar)bb66a11
Fix DoS vulnerability in mempool acceptance (Suhas Daftuar)
This commit is contained in:
commit
ca40ef6029
2 changed files with 56 additions and 8 deletions
|
@ -43,6 +43,7 @@ class TestNode(NodeConnCB):
|
||||||
self.last_pong = msg_pong(0)
|
self.last_pong = msg_pong(0)
|
||||||
self.sleep_time = 0.05
|
self.sleep_time = 0.05
|
||||||
self.getdataset = set()
|
self.getdataset = set()
|
||||||
|
self.last_reject = None
|
||||||
|
|
||||||
def add_connection(self, conn):
|
def add_connection(self, conn):
|
||||||
self.connection = conn
|
self.connection = conn
|
||||||
|
@ -68,7 +69,7 @@ class TestNode(NodeConnCB):
|
||||||
|
|
||||||
def on_reject(self, conn, message):
|
def on_reject(self, conn, message):
|
||||||
self.last_reject = message
|
self.last_reject = message
|
||||||
#print message
|
#print (message)
|
||||||
|
|
||||||
# Syncing helpers
|
# Syncing helpers
|
||||||
def sync(self, test_function, timeout=60):
|
def sync(self, test_function, timeout=60):
|
||||||
|
@ -136,13 +137,17 @@ class TestNode(NodeConnCB):
|
||||||
self.wait_for_block(blockhash, timeout)
|
self.wait_for_block(blockhash, timeout)
|
||||||
return self.last_block
|
return self.last_block
|
||||||
|
|
||||||
def test_transaction_acceptance(self, tx, with_witness, accepted):
|
def test_transaction_acceptance(self, tx, with_witness, accepted, reason=None):
|
||||||
tx_message = msg_tx(tx)
|
tx_message = msg_tx(tx)
|
||||||
if with_witness:
|
if with_witness:
|
||||||
tx_message = msg_witness_tx(tx)
|
tx_message = msg_witness_tx(tx)
|
||||||
self.send_message(tx_message)
|
self.send_message(tx_message)
|
||||||
self.sync_with_ping()
|
self.sync_with_ping()
|
||||||
assert_equal(tx.hash in self.connection.rpc.getrawmempool(), accepted)
|
assert_equal(tx.hash in self.connection.rpc.getrawmempool(), accepted)
|
||||||
|
if (reason != None and not accepted):
|
||||||
|
# Check the rejection reason as well.
|
||||||
|
with mininode_lock:
|
||||||
|
assert_equal(self.last_reject.reason, reason)
|
||||||
|
|
||||||
# Test whether a witness block had the correct effect on the tip
|
# Test whether a witness block had the correct effect on the tip
|
||||||
def test_witness_block(self, block, accepted, with_witness=True):
|
def test_witness_block(self, block, accepted, with_witness=True):
|
||||||
|
@ -277,9 +282,52 @@ class SegWitTest(BitcoinTestFramework):
|
||||||
self.test_node.sync_with_ping()
|
self.test_node.sync_with_ping()
|
||||||
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
|
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
|
||||||
|
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
|
# Create a p2sh output -- this is so we can pass the standardness
|
||||||
|
# rules (an anyone-can-spend OP_TRUE would be rejected, if not wrapped
|
||||||
|
# in P2SH).
|
||||||
|
p2sh_program = CScript([OP_TRUE])
|
||||||
|
p2sh_pubkey = hash160(p2sh_program)
|
||||||
|
scriptPubKey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL])
|
||||||
|
|
||||||
|
# Now check that unnecessary witnesses can't be used to blind a node
|
||||||
|
# to a transaction, eg by violating standardness checks.
|
||||||
|
tx2 = CTransaction()
|
||||||
|
tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b""))
|
||||||
|
tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, scriptPubKey))
|
||||||
|
tx2.rehash()
|
||||||
|
self.test_node.test_transaction_acceptance(tx2, False, True)
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
|
# We'll add an unnecessary witness to this transaction that would cause
|
||||||
|
# it to be too large according to IsStandard.
|
||||||
|
tx3 = CTransaction()
|
||||||
|
tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), CScript([p2sh_program])))
|
||||||
|
tx3.vout.append(CTxOut(tx2.vout[0].nValue-1000, scriptPubKey))
|
||||||
|
tx3.wit.vtxinwit.append(CTxinWitness())
|
||||||
|
tx3.wit.vtxinwit[0].scriptWitness.stack = [b'a'*400000]
|
||||||
|
tx3.rehash()
|
||||||
|
self.std_node.test_transaction_acceptance(tx3, True, False, b'no-witness-yet')
|
||||||
|
|
||||||
|
# If we send without witness, it should be accepted.
|
||||||
|
self.std_node.test_transaction_acceptance(tx3, False, True)
|
||||||
|
|
||||||
|
# Now create a new anyone-can-spend utxo for the next test.
|
||||||
|
tx4 = CTransaction()
|
||||||
|
tx4.vin.append(CTxIn(COutPoint(tx3.sha256, 0), CScript([p2sh_program])))
|
||||||
|
tx4.vout.append(CTxOut(tx3.vout[0].nValue-1000, CScript([OP_TRUE])))
|
||||||
|
tx4.rehash()
|
||||||
|
self.test_node.test_transaction_acceptance(tx3, False, True)
|
||||||
|
self.test_node.test_transaction_acceptance(tx4, False, True)
|
||||||
|
|
||||||
|
self.nodes[0].generate(1)
|
||||||
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
# Update our utxo list; we spent the first entry.
|
# Update our utxo list; we spent the first entry.
|
||||||
self.utxo.pop(0)
|
self.utxo.pop(0)
|
||||||
self.utxo.append(UTXO(tx.sha256, 0, tx.vout[0].nValue))
|
self.utxo.append(UTXO(tx4.sha256, 0, tx4.vout[0].nValue))
|
||||||
|
|
||||||
|
|
||||||
# Mine enough blocks for segwit's vb state to be 'started'.
|
# Mine enough blocks for segwit's vb state to be 'started'.
|
||||||
|
|
10
src/main.cpp
10
src/main.cpp
|
@ -1132,11 +1132,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
||||||
if (tx.IsCoinBase())
|
if (tx.IsCoinBase())
|
||||||
return state.DoS(100, false, REJECT_INVALID, "coinbase");
|
return state.DoS(100, false, REJECT_INVALID, "coinbase");
|
||||||
|
|
||||||
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
|
|
||||||
string reason;
|
|
||||||
if (fRequireStandard && !IsStandardTx(tx, reason))
|
|
||||||
return state.DoS(0, false, REJECT_NONSTANDARD, reason);
|
|
||||||
|
|
||||||
// Don't relay version 2 transactions until CSV is active, and we can be
|
// Don't relay version 2 transactions until CSV is active, and we can be
|
||||||
// sure that such transactions will be mined (unless we're on
|
// sure that such transactions will be mined (unless we're on
|
||||||
// -testnet/-regtest).
|
// -testnet/-regtest).
|
||||||
|
@ -1150,6 +1145,11 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
|
||||||
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)
|
||||||
|
string reason;
|
||||||
|
if (fRequireStandard && !IsStandardTx(tx, 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
|
||||||
// block; we don't want our mempool filled up with transactions that can't
|
// block; we don't want our mempool filled up with transactions that can't
|
||||||
// be mined yet.
|
// be mined yet.
|
||||||
|
|
Loading…
Reference in a new issue