mirror of
https://github.com/dashpay/dash.git
synced 2024-12-28 05:23:01 +01:00
5454bea377
* add locktransaction rpc call * Remove special instantsend fee for simple transactions * Function to check if trx is simple enough to be autolocked * Automatic lock for all received from peers simple trxes If we get a new transaction with CInv message and it is "simple" and is accepted in mempool, we initiate its lock. We don't lock orphan trxes that accepted in mempool after this trx because they are locked by other peers. * Automatically lock simple trxes in wallet * protocol bump for InstantSend without special fee * Add function to detect used mempool share * Mempool threshold for auto IX locks * Add SPORK_16_INSTANTSEND_AUTOLOCKS spork * Make autolocks active only when spork SPORK_16_INSTANTSEND_AUTOLOCKS is active * BIP9 autolocks activation * revert increasing min peer protocol version for mn rank * move IsTrxSimple check to CTxLockRequest class * make MAX_INPUTS_FOR_AUTO_IX private member of CTxLockRequest class * make AUTO_IX_MEMPOOL_THRESHOLD private member of CInstantSend class * remove locktransaction RPC call * tests for automatic IS locks * fix mempool threshod calculation * bump mocktime in activate_autoix_bip9 * set node times * no need to spam the node with gettransaction rpc requests that often * use `spork active` instead of leaking spork logic into tests * codestyle fixes * add test description in comments * fix typo * sync test nodes more often during BIP9 activation * Use 4th bit in BIP9 activation * Fix comments according codestyle guide * Call AcceptLockRequest and Vote at the first node creating autoix lock * fix mempool used memory calculation * rallback not necessary change in CWallet::CreateTransaction * test for stopping autolocks for full mempool * Inject "simple autolockable" txes into txlockrequest logic
307 lines
11 KiB
Python
Executable File
307 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2018 The Dash Core developers
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
from test_framework.mininode import *
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import *
|
|
from time import *
|
|
|
|
'''
|
|
autoix-mempool.py
|
|
|
|
Checks if automatic InstantSend locks stop working when transaction mempool
|
|
is full (more than 0.1 part from max value).
|
|
|
|
'''
|
|
|
|
MASTERNODE_COLLATERAL = 1000
|
|
MAX_MEMPOOL_SIZE = 5 # max node mempool in MBs
|
|
MB_SIZE = 1000000 # C++ code use this coefficient to calc MB in mempool
|
|
AUTO_IX_MEM_THRESHOLD = 0.1
|
|
|
|
|
|
class MasternodeInfo:
|
|
def __init__(self, key, collateral_id, collateral_out):
|
|
self.key = key
|
|
self.collateral_id = collateral_id
|
|
self.collateral_out = collateral_out
|
|
|
|
|
|
class AutoIXMempoolTest(BitcoinTestFramework):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.mn_count = 10
|
|
self.num_nodes = self.mn_count + 3
|
|
self.mninfo = []
|
|
self.setup_clean_chain = True
|
|
self.is_network_split = False
|
|
# set sender, receiver, isolated nodes
|
|
self.receiver_idx = self.num_nodes - 2
|
|
self.sender_idx = self.num_nodes - 3
|
|
# additional args
|
|
self.extra_args = ["-maxmempool=%d" % MAX_MEMPOOL_SIZE]
|
|
|
|
def create_simple_node(self):
|
|
idx = len(self.nodes)
|
|
args = ["-debug"] + self.extra_args
|
|
self.nodes.append(start_node(idx, self.options.tmpdir,
|
|
args))
|
|
for i in range(0, idx):
|
|
connect_nodes(self.nodes[i], idx)
|
|
|
|
def get_mnconf_file(self):
|
|
return os.path.join(self.options.tmpdir, "node0/regtest/masternode.conf")
|
|
|
|
def prepare_masternodes(self):
|
|
for idx in range(0, self.mn_count):
|
|
key = self.nodes[0].masternode("genkey")
|
|
address = self.nodes[0].getnewaddress()
|
|
txid = self.nodes[0].sendtoaddress(address, MASTERNODE_COLLATERAL)
|
|
txrow = self.nodes[0].getrawtransaction(txid, True)
|
|
collateral_vout = 0
|
|
for vout_idx in range(0, len(txrow["vout"])):
|
|
vout = txrow["vout"][vout_idx]
|
|
if vout["value"] == MASTERNODE_COLLATERAL:
|
|
collateral_vout = vout_idx
|
|
self.nodes[0].lockunspent(False,
|
|
[{"txid": txid, "vout": collateral_vout}])
|
|
self.mninfo.append(MasternodeInfo(key, txid, collateral_vout))
|
|
|
|
def write_mn_config(self):
|
|
conf = self.get_mnconf_file()
|
|
f = open(conf, 'a')
|
|
for idx in range(0, self.mn_count):
|
|
f.write("mn%d 127.0.0.1:%d %s %s %d\n" % (idx + 1, p2p_port(idx + 1),
|
|
self.mninfo[idx].key,
|
|
self.mninfo[idx].collateral_id,
|
|
self.mninfo[idx].collateral_out))
|
|
f.close()
|
|
|
|
def create_masternodes(self):
|
|
for idx in range(0, self.mn_count):
|
|
args = ['-debug=masternode', '-externalip=127.0.0.1', '-masternode=1',
|
|
'-masternodeprivkey=%s' % self.mninfo[idx].key] + self.extra_args
|
|
self.nodes.append(start_node(idx + 1, self.options.tmpdir, args))
|
|
for i in range(0, idx + 1):
|
|
connect_nodes(self.nodes[i], idx + 1)
|
|
|
|
def setup_network(self):
|
|
self.nodes = []
|
|
# create faucet node for collateral and transactions
|
|
args = ["-debug"] + self.extra_args
|
|
self.nodes.append(start_node(0, self.options.tmpdir, args))
|
|
required_balance = MASTERNODE_COLLATERAL * self.mn_count + 1
|
|
while self.nodes[0].getbalance() < required_balance:
|
|
set_mocktime(get_mocktime() + 1)
|
|
set_node_times(self.nodes, get_mocktime())
|
|
self.nodes[0].generate(1)
|
|
# create masternodes
|
|
self.prepare_masternodes()
|
|
self.write_mn_config()
|
|
stop_node(self.nodes[0], 0)
|
|
args = ["-debug",
|
|
"-sporkkey=cP4EKFyJsHT39LDqgdcB43Y3YXjNyjb5Fuas1GQSeAtjnZWmZEQK"] + \
|
|
self.extra_args
|
|
self.nodes[0] = start_node(0, self.options.tmpdir,
|
|
args)
|
|
self.create_masternodes()
|
|
# create connected simple nodes
|
|
for i in range(0, self.num_nodes - self.mn_count - 1):
|
|
self.create_simple_node()
|
|
set_mocktime(get_mocktime() + 1)
|
|
set_node_times(self.nodes, get_mocktime())
|
|
self.nodes[0].generate(1)
|
|
# sync nodes
|
|
self.sync_all()
|
|
set_mocktime(get_mocktime() + 1)
|
|
set_node_times(self.nodes, get_mocktime())
|
|
sync_masternodes(self.nodes)
|
|
for i in range(1, self.mn_count + 1):
|
|
res = self.nodes[0].masternode("start-alias", "mn%d" % i)
|
|
assert(res["result"] == 'successful')
|
|
sync_masternodes(self.nodes)
|
|
mn_info = self.nodes[0].masternodelist("status")
|
|
assert(len(mn_info) == self.mn_count)
|
|
for status in mn_info.values():
|
|
assert(status == 'ENABLED')
|
|
|
|
def get_autoix_bip9_status(self):
|
|
info = self.nodes[0].getblockchaininfo()
|
|
return info['bip9_softforks']['autoix']['status']
|
|
|
|
def activate_autoix_bip9(self):
|
|
# sync nodes periodically
|
|
# if we sync them too often, activation takes too many time
|
|
# if we sync them too rarely, nodes failed to update its state and
|
|
# bip9 status is not updated
|
|
# so, in this code nodes are synced once per 20 blocks
|
|
counter = 0
|
|
sync_period = 10
|
|
|
|
while self.get_autoix_bip9_status() == 'defined':
|
|
set_mocktime(get_mocktime() + 1)
|
|
set_node_times(self.nodes, get_mocktime())
|
|
self.nodes[0].generate(1)
|
|
counter += 1
|
|
if counter % sync_period == 0:
|
|
# sync nodes
|
|
self.sync_all()
|
|
sync_masternodes(self.nodes)
|
|
|
|
while self.get_autoix_bip9_status() == 'started':
|
|
set_mocktime(get_mocktime() + 1)
|
|
set_node_times(self.nodes, get_mocktime())
|
|
self.nodes[0].generate(1)
|
|
counter += 1
|
|
if counter % sync_period == 0:
|
|
# sync nodes
|
|
self.sync_all()
|
|
sync_masternodes(self.nodes)
|
|
|
|
while self.get_autoix_bip9_status() == 'locked_in':
|
|
set_mocktime(get_mocktime() + 1)
|
|
set_node_times(self.nodes, get_mocktime())
|
|
self.nodes[0].generate(1)
|
|
counter += 1
|
|
if counter % sync_period == 0:
|
|
# sync nodes
|
|
self.sync_all()
|
|
sync_masternodes(self.nodes)
|
|
|
|
# sync nodes
|
|
self.sync_all()
|
|
sync_masternodes(self.nodes)
|
|
|
|
assert(self.get_autoix_bip9_status() == 'active')
|
|
|
|
def get_autoix_spork_state(self):
|
|
info = self.nodes[0].spork('active')
|
|
return info['SPORK_16_INSTANTSEND_AUTOLOCKS']
|
|
|
|
def set_autoix_spork_state(self, state):
|
|
if state:
|
|
value = 0
|
|
else:
|
|
value = 4070908800
|
|
self.nodes[0].spork('SPORK_16_INSTANTSEND_AUTOLOCKS', value)
|
|
|
|
def enforce_masternode_payments(self):
|
|
self.nodes[0].spork('SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT', 0)
|
|
|
|
def create_raw_trx(self, node_from, node_to, amount, min_inputs, max_inputs):
|
|
assert(min_inputs <= max_inputs)
|
|
# fill inputs
|
|
inputs=[]
|
|
balances = node_from.listunspent()
|
|
in_amount = 0.0
|
|
last_amount = 0.0
|
|
for tx in balances:
|
|
if len(inputs) < min_inputs:
|
|
input = {}
|
|
input["txid"] = tx['txid']
|
|
input['vout'] = tx['vout']
|
|
in_amount += float(tx['amount'])
|
|
inputs.append(input)
|
|
elif in_amount > amount:
|
|
break
|
|
elif len(inputs) < max_inputs:
|
|
input = {}
|
|
input["txid"] = tx['txid']
|
|
input['vout'] = tx['vout']
|
|
in_amount += float(tx['amount'])
|
|
inputs.append(input)
|
|
else:
|
|
input = {}
|
|
input["txid"] = tx['txid']
|
|
input['vout'] = tx['vout']
|
|
in_amount -= last_amount
|
|
in_amount += float(tx['amount'])
|
|
inputs[-1] = input
|
|
last_amount = float(tx['amount'])
|
|
|
|
assert(len(inputs) > 0)
|
|
assert(in_amount > amount)
|
|
# fill outputs
|
|
receiver_address = node_to.getnewaddress()
|
|
change_address = node_from.getnewaddress()
|
|
fee = 0.001
|
|
outputs={}
|
|
outputs[receiver_address] = amount
|
|
outputs[change_address] = in_amount - amount - fee
|
|
rawtx = node_from.createrawtransaction(inputs, outputs)
|
|
return node_from.signrawtransaction(rawtx)
|
|
|
|
def check_IX_lock(self, txid, node):
|
|
# wait for instantsend locks
|
|
start = time()
|
|
locked = False
|
|
while True:
|
|
is_trx = node.gettransaction(txid)
|
|
if is_trx['instantlock']:
|
|
locked = True
|
|
break
|
|
if time() > start + 10:
|
|
break
|
|
sleep(0.1)
|
|
return locked
|
|
|
|
# sends regular IX with high fee and may inputs (not-simple transaction)
|
|
def send_regular_IX(self, sender, receiver):
|
|
receiver_addr = receiver.getnewaddress()
|
|
txid = sender.instantsendtoaddress(receiver_addr, 1.0)
|
|
return self.check_IX_lock(txid, sender)
|
|
|
|
# sends simple trx, it should become IX if autolocks are allowed
|
|
def send_simple_tx(self, sender, receiver):
|
|
raw_tx = self.create_raw_trx(sender, receiver, 1.0, 1, 4)
|
|
txid = self.nodes[0].sendrawtransaction(raw_tx['hex'])
|
|
self.sync_all()
|
|
return self.check_IX_lock(txid, sender)
|
|
|
|
def get_mempool_size(self, node):
|
|
info = node.getmempoolinfo()
|
|
return info['usage']
|
|
|
|
def fill_mempool(self):
|
|
node = self.nodes[0]
|
|
rec_address = node.getnewaddress()
|
|
while self.get_mempool_size(node) < MAX_MEMPOOL_SIZE * MB_SIZE * AUTO_IX_MEM_THRESHOLD + 10000:
|
|
node.sendtoaddress(rec_address, 1.0)
|
|
sleep(0.1)
|
|
self.sync_all()
|
|
|
|
def run_test(self):
|
|
self.enforce_masternode_payments() # required for bip9 activation
|
|
self.activate_autoix_bip9()
|
|
self.set_autoix_spork_state(True)
|
|
|
|
# check pre-conditions for autoIX
|
|
assert(self.get_autoix_bip9_status() == 'active')
|
|
assert(self.get_autoix_spork_state())
|
|
|
|
# autoIX is working
|
|
assert(self.send_simple_tx(self.nodes[0], self.nodes[self.receiver_idx]))
|
|
|
|
# send funds for InstantSend after filling mempool and give them 6 confirmations
|
|
rec_address = self.nodes[self.receiver_idx].getnewaddress()
|
|
self.nodes[0].sendtoaddress(rec_address, 500.0)
|
|
self.nodes[0].sendtoaddress(rec_address, 500.0)
|
|
self.sync_all()
|
|
for i in range(0, 2):
|
|
self.nodes[self.receiver_idx].generate(1)
|
|
self.sync_all()
|
|
|
|
# fill mempool with transactions
|
|
self.fill_mempool()
|
|
|
|
# autoIX is not working now
|
|
assert(not self.send_simple_tx(self.nodes[self.receiver_idx], self.nodes[0]))
|
|
# regular IX is still working
|
|
assert(self.send_regular_IX(self.nodes[self.receiver_idx], self.nodes[0]))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
AutoIXMempoolTest().main()
|