Skip to content

Commit dd27e11

Browse files
authored
test: asset unlock (withdrawal) should not be Instant-Send locked (dashpay#5862)
## Issue being fixed or feature implemented To prevent spending withdrawal before that's finalized, mined and chainlocked, next things should be true: - txes with spending of unlock and unlock themselves do not receive islocks. That's true, because IS excluded for txes with no inputs. - When the unlock is removed from mempool, so are the children. These functionality has no tests, but that's crucial for consensus be fine. ## What was done? Implemented checks to be sure that IS is not send for Withdrawal txes. Functional test for asset_locks.py are refactored a bit to make it easier to read. ## How Has This Been Tested? This PR is tests ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone
1 parent 6c06132 commit dd27e11

File tree

1 file changed

+78
-35
lines changed

1 file changed

+78
-35
lines changed

test/functional/feature_asset_locks.py

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ def check_mempool_size(self):
152152
def check_mempool_result(self, result_expected, tx):
153153
"""Wrapper to check result of testmempoolaccept on node_0's mempool"""
154154
result_expected['txid'] = tx.rehash()
155+
if result_expected['allowed']:
156+
result_expected['vsize'] = tx.get_vsize()
155157

156158
result_test = self.nodes[0].testmempoolaccept([tx.serialize().hex()])
157159

@@ -190,7 +192,6 @@ def set_sporks(self):
190192

191193
self.nodes[0].sporkupdate("SPORK_17_QUORUM_DKG_ENABLED", spork_enabled)
192194
self.nodes[0].sporkupdate("SPORK_19_CHAINLOCKS_ENABLED", spork_disabled)
193-
self.nodes[0].sporkupdate("SPORK_3_INSTANTSEND_BLOCK_FILTERING", spork_disabled)
194195
self.nodes[0].sporkupdate("SPORK_2_INSTANTSEND_ENABLED", spork_disabled)
195196
self.wait_for_sporks_same()
196197

@@ -261,6 +262,13 @@ def run_test(self):
261262
key.generate()
262263
pubkey = key.get_pubkey().get_bytes()
263264

265+
self.test_asset_locks(node_wallet, node, pubkey)
266+
self.test_asset_unlocks(node_wallet, node, pubkey)
267+
self.test_withdrawal_limits(node_wallet, node, pubkey)
268+
self.test_mn_rr(node_wallet, node, pubkey)
269+
270+
271+
def test_asset_locks(self, node_wallet, node, pubkey):
264272
self.log.info("Testing asset lock...")
265273
locked_1 = 10 * COIN + 141421
266274
locked_2 = 10 * COIN + 314159
@@ -272,7 +280,7 @@ def run_test(self):
272280
asset_lock_tx = self.create_assetlock(coin, locked_1, pubkey)
273281

274282

275-
self.check_mempool_result(tx=asset_lock_tx, result_expected={'allowed': True, 'vsize': asset_lock_tx.get_vsize(), 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
283+
self.check_mempool_result(tx=asset_lock_tx, result_expected={'allowed': True, 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
276284
self.validate_credit_pool_balance(0)
277285
txid_in_block = self.send_tx(asset_lock_tx)
278286
assert "assetLockTx" in node.getrawtransaction(txid_in_block, 1)
@@ -287,9 +295,9 @@ def run_test(self):
287295

288296
# tx is mined, let's get blockhash
289297
self.log.info("Invalidate block with asset lock tx...")
290-
block_hash_1 = node_wallet.gettransaction(txid_in_block)['blockhash']
298+
self.block_hash_1 = node_wallet.gettransaction(txid_in_block)['blockhash']
291299
for inode in self.nodes:
292-
inode.invalidateblock(block_hash_1)
300+
inode.invalidateblock(self.block_hash_1)
293301
assert_equal(self.get_credit_pool_balance(node=inode), 0)
294302
node.generate(3)
295303
self.sync_all()
@@ -303,7 +311,7 @@ def run_test(self):
303311
self.validate_credit_pool_balance(locked_2)
304312
self.log.info("Reconsider old blocks...")
305313
for inode in self.nodes:
306-
inode.reconsiderblock(block_hash_1)
314+
inode.reconsiderblock(self.block_hash_1)
307315
self.validate_credit_pool_balance(locked_1)
308316
self.sync_all()
309317

@@ -317,10 +325,14 @@ def run_test(self):
317325

318326
self.validate_credit_pool_balance(locked_1)
319327

328+
329+
def test_asset_unlocks(self, node_wallet, node, pubkey):
320330
self.log.info("Testing asset unlock...")
321331

322332
self.log.info("Generating several txes by same quorum....")
323-
self.validate_credit_pool_balance(locked_1)
333+
locked = self.get_credit_pool_balance()
334+
335+
self.validate_credit_pool_balance(locked)
324336
asset_unlock_tx = self.create_assetunlock(101, COIN, pubkey)
325337
asset_unlock_tx_late = self.create_assetunlock(102, COIN, pubkey)
326338
asset_unlock_tx_too_late = self.create_assetunlock(103, COIN, pubkey)
@@ -331,7 +343,7 @@ def run_test(self):
331343
asset_unlock_tx_duplicate_index.vout[0].nValue += COIN
332344
too_late_height = node.getblockcount() + 48
333345

334-
self.check_mempool_result(tx=asset_unlock_tx, result_expected={'allowed': True, 'vsize': asset_unlock_tx.get_vsize(), 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
346+
self.check_mempool_result(tx=asset_unlock_tx, result_expected={'allowed': True, 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
335347
self.check_mempool_result(tx=asset_unlock_tx_too_big_fee,
336348
result_expected={'allowed': False, 'reject-reason' : 'max-fee-exceeded'})
337349
self.check_mempool_result(tx=asset_unlock_tx_zero_fee,
@@ -346,8 +358,15 @@ def run_test(self):
346358

347359
assert_equal(asset_unlock_tx_payload.quorumHash, int(self.mninfo[0].node.quorum("selectquorum", llmq_type_test, 'e6c7a809d79f78ea85b72d5df7e9bd592aecf151e679d6e976b74f053a7f9056')["quorumHash"], 16))
348360

361+
self.log.info("Test no IS for asset unlock...")
362+
self.nodes[0].sporkupdate("SPORK_2_INSTANTSEND_ENABLED", 0)
363+
self.wait_for_sporks_same()
364+
349365
txid = self.send_tx(asset_unlock_tx)
350-
assert "assetUnlockTx" in node.getrawtransaction(txid, 1)
366+
is_id = node_wallet.sendtoaddress(node_wallet.getnewaddress(), 1)
367+
for node in self.nodes:
368+
self.wait_for_instantlock(is_id, node)
369+
351370

352371
tip = self.nodes[0].getblockcount()
353372
indexes_statuses_no_height = self.nodes[0].getassetunlockstatuses(["101", "102", "300"])
@@ -356,13 +375,30 @@ def run_test(self):
356375
assert_equal([{'index': 101, 'status': 'unknown'}, {'index': 102, 'status': 'unknown'}, {'index': 300, 'status': 'unknown'}], indexes_statuses_height)
357376

358377

359-
self.mempool_size += 1
378+
rawtx = node.getrawtransaction(txid, 1)
379+
rawtx_is = node.getrawtransaction(is_id, 1)
380+
assert_equal(rawtx["instantlock"], False)
381+
assert_equal(rawtx_is["instantlock"], True)
382+
assert_equal(rawtx["chainlock"], False)
383+
assert_equal(rawtx_is["chainlock"], False)
384+
assert not "confirmations" in rawtx
385+
assert not "confirmations" in rawtx_is
386+
# disable back IS
387+
self.set_sporks()
388+
389+
assert "assetUnlockTx" in node.getrawtransaction(txid, 1)
390+
391+
self.mempool_size += 2
360392
self.check_mempool_size()
361-
self.validate_credit_pool_balance(locked_1)
393+
self.validate_credit_pool_balance(locked)
362394
node.generate(1)
363395
self.sync_all()
364-
self.validate_credit_pool_balance(locked_1 - COIN)
365-
self.mempool_size -= 1
396+
assert_equal(rawtx["instantlock"], False)
397+
assert_equal(rawtx["chainlock"], False)
398+
rawtx = node.getrawtransaction(txid, 1)
399+
assert_equal(rawtx["confirmations"], 1)
400+
self.validate_credit_pool_balance(locked - COIN)
401+
self.mempool_size -= 2
366402
self.check_mempool_size()
367403
block_asset_unlock = node.getrawtransaction(asset_unlock_tx.rehash(), 1)['blockhash']
368404

@@ -373,18 +409,18 @@ def run_test(self):
373409
self.log.info("Mining next quorum to check tx 'asset_unlock_tx_late' is still valid...")
374410
self.mine_quorum(llmq_type_name="llmq_test_platform", llmq_type=106)
375411
self.log.info("Checking credit pool amount is same...")
376-
self.validate_credit_pool_balance(locked_1 - 1 * COIN)
377-
self.check_mempool_result(tx=asset_unlock_tx_late, result_expected={'allowed': True, 'vsize': asset_unlock_tx_late.get_vsize(), 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
412+
self.validate_credit_pool_balance(locked - 1 * COIN)
413+
self.check_mempool_result(tx=asset_unlock_tx_late, result_expected={'allowed': True, 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
378414
self.log.info("Checking credit pool amount still is same...")
379-
self.validate_credit_pool_balance(locked_1 - 1 * COIN)
415+
self.validate_credit_pool_balance(locked - 1 * COIN)
380416
self.send_tx(asset_unlock_tx_late)
381417
node.generate(1)
382418
self.sync_all()
383-
self.validate_credit_pool_balance(locked_1 - 2 * COIN)
419+
self.validate_credit_pool_balance(locked - 2 * COIN)
384420

385421
self.log.info("Generating many blocks to make quorum far behind (even still active)...")
386422
self.slowly_generate_batch(too_late_height - node.getblockcount() - 1)
387-
self.check_mempool_result(tx=asset_unlock_tx_too_late, result_expected={'allowed': True, 'vsize': asset_unlock_tx_too_late.get_vsize(), 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
423+
self.check_mempool_result(tx=asset_unlock_tx_too_late, result_expected={'allowed': True, 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
388424
node.generate(1)
389425
self.sync_all()
390426
self.check_mempool_result(tx=asset_unlock_tx_too_late,
@@ -400,28 +436,34 @@ def run_test(self):
400436
self.log.info("Test block invalidation with asset unlock tx...")
401437
for inode in self.nodes:
402438
inode.invalidateblock(block_asset_unlock)
403-
self.validate_credit_pool_balance(locked_1)
439+
self.validate_credit_pool_balance(locked)
404440
self.slowly_generate_batch(50)
405-
self.validate_credit_pool_balance(locked_1)
441+
self.validate_credit_pool_balance(locked)
406442
for inode in self.nodes:
407443
inode.reconsiderblock(block_to_reconsider)
408-
self.validate_credit_pool_balance(locked_1 - 2 * COIN)
444+
self.validate_credit_pool_balance(locked - 2 * COIN)
409445

410446
self.log.info("Forcibly mining asset_unlock_tx_too_late and ensure block is invalid...")
411447
self.create_and_check_block([asset_unlock_tx_too_late], expected_error = "bad-assetunlock-not-active-quorum")
412448

413449
node.generate(1)
414450
self.sync_all()
415451

416-
self.validate_credit_pool_balance(locked_1 - 2 * COIN)
417-
self.validate_credit_pool_balance(block_hash=block_hash_1, expected=locked_1)
452+
self.validate_credit_pool_balance(locked - 2 * COIN)
453+
self.validate_credit_pool_balance(block_hash=self.block_hash_1, expected=locked)
454+
455+
self.log.info("Forcibly mine asset_unlock_tx_full and ensure block is invalid...")
456+
self.create_and_check_block([asset_unlock_tx_duplicate_index], expected_error = "bad-assetunlock-duplicated-index")
457+
418458

419-
self.log.info("Checking too big withdrawal... expected to not be mined")
459+
def test_withdrawal_limits(self, node_wallet, node, pubkey):
460+
self.log.info("Testing withdrawal limits...")
461+
self.log.info("Too big withdrawal is expected to not be mined")
420462
asset_unlock_tx_full = self.create_assetunlock(201, 1 + self.get_credit_pool_balance(), pubkey)
421463

422464
self.log.info("Checking that transaction with exceeding amount accepted by mempool...")
423465
# Mempool doesn't know about the size of the credit pool
424-
self.check_mempool_result(tx=asset_unlock_tx_full, result_expected={'allowed': True, 'vsize': asset_unlock_tx_full.get_vsize(), 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
466+
self.check_mempool_result(tx=asset_unlock_tx_full, result_expected={'allowed': True, 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
425467

426468
txid_in_block = self.send_tx(asset_unlock_tx_full)
427469
node.generate(1)
@@ -434,7 +476,7 @@ def run_test(self):
434476

435477
self.mempool_size += 1
436478
asset_unlock_tx_full = self.create_assetunlock(301, self.get_credit_pool_balance(), pubkey)
437-
self.check_mempool_result(tx=asset_unlock_tx_full, result_expected={'allowed': True, 'vsize': asset_unlock_tx_full.get_vsize(), 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
479+
self.check_mempool_result(tx=asset_unlock_tx_full, result_expected={'allowed': True, 'fees': {'base': Decimal(str(tiny_amount / COIN))}})
438480

439481
txid_in_block = self.send_tx(asset_unlock_tx_full)
440482
node.generate(1)
@@ -444,14 +486,12 @@ def run_test(self):
444486
assert txid_in_block in block['tx']
445487
self.validate_credit_pool_balance(0)
446488

447-
self.log.info("Forcibly mine asset_unlock_tx_full and ensure block is invalid...")
448-
self.create_and_check_block([asset_unlock_tx_duplicate_index], expected_error = "bad-assetunlock-duplicated-index")
449-
450489
self.log.info("Fast forward to the next day to reset all current unlock limits...")
451490
self.slowly_generate_batch(blocks_in_one_day + 1)
452491
self.mine_quorum(llmq_type_name="llmq_test_platform", llmq_type=106)
453492

454493
total = self.get_credit_pool_balance()
494+
coins = node_wallet.listunspent()
455495
while total <= 10_900 * COIN:
456496
self.log.info(f"Collecting coins in pool... Collected {total}/{10_900 * COIN}")
457497
coin = coins.pop()
@@ -541,11 +581,13 @@ def run_test(self):
541581
assert_equal(new_total, self.get_credit_pool_balance())
542582
self.check_mempool_size()
543583

544-
# activate MN_RR reallocation
584+
585+
def test_mn_rr(self, node_wallet, node, pubkey):
545586
self.log.info("Activate mn_rr...")
587+
locked = self.get_credit_pool_balance()
546588
self.activate_mn_rr(expected_activation_height=node.getblockcount() + 12 * 3)
547589
self.log.info(f'height: {node.getblockcount()} credit: {self.get_credit_pool_balance()}')
548-
assert_equal(new_total, self.get_credit_pool_balance())
590+
assert_equal(locked, self.get_credit_pool_balance())
549591

550592
bt = node.getblocktemplate()
551593
platform_reward = bt['masternode'][0]['amount']
@@ -556,18 +598,19 @@ def run_test(self):
556598
assert_equal(all_mn_rewards, bt['coinbasevalue'] * 3 // 4) # 75/25 mn/miner reward split
557599
assert_equal(platform_reward, all_mn_rewards * 375 // 1000) # 0.375 platform share
558600
assert_equal(platform_reward, 31916328)
559-
assert_equal(new_total, self.get_credit_pool_balance())
601+
assert_equal(locked, self.get_credit_pool_balance())
560602
node.generate(1)
561603
self.sync_all()
562-
new_total += platform_reward
563-
assert_equal(new_total, self.get_credit_pool_balance())
604+
locked += platform_reward
605+
assert_equal(locked, self.get_credit_pool_balance())
564606

607+
coins = node_wallet.listunspent()
565608
coin = coins.pop()
566609
self.send_tx(self.create_assetlock(coin, COIN, pubkey))
567-
new_total += platform_reward + COIN
610+
locked += platform_reward + COIN
568611
node.generate(1)
569612
self.sync_all()
570-
assert_equal(new_total, self.get_credit_pool_balance())
613+
assert_equal(locked, self.get_credit_pool_balance())
571614

572615

573616
if __name__ == '__main__':

0 commit comments

Comments
 (0)