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
301 lines
11 KiB
Python
Executable File
301 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 *
|
|
|
|
'''
|
|
p2p-autoinstantsend.py
|
|
|
|
Test automatic InstantSend locks functionality.
|
|
|
|
Checks that simple transactions automatically become InstantSend locked,
|
|
complex transactions don't become IS-locked and this functionality is
|
|
activated only if it is BIP9-activated and SPORK_16_INSTANTSEND_AUTOLOCKS is
|
|
active.
|
|
|
|
Also checks that this functionality doesn't influence regular InstantSend
|
|
transactions with high fee.
|
|
'''
|
|
|
|
MASTERNODE_COLLATERAL = 1000
|
|
|
|
|
|
class MasternodeInfo:
|
|
def __init__(self, key, collateral_id, collateral_out):
|
|
self.key = key
|
|
self.collateral_id = collateral_id
|
|
self.collateral_out = collateral_out
|
|
|
|
|
|
class AutoInstantSendTest(BitcoinTestFramework):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.mn_count = 10
|
|
self.num_nodes = self.mn_count + 4
|
|
self.mninfo = []
|
|
self.setup_clean_chain = True
|
|
self.is_network_split = False
|
|
# set sender, receiver, isolated nodes
|
|
self.isolated_idx = self.num_nodes - 1
|
|
self.receiver_idx = self.num_nodes - 2
|
|
self.sender_idx = self.num_nodes - 3
|
|
|
|
def create_simple_node(self):
|
|
idx = len(self.nodes)
|
|
self.nodes.append(start_node(idx, self.options.tmpdir,
|
|
["-debug"]))
|
|
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):
|
|
self.nodes.append(start_node(idx + 1, self.options.tmpdir,
|
|
['-debug=masternode', '-externalip=127.0.0.1',
|
|
'-masternode=1',
|
|
'-masternodeprivkey=%s' % self.mninfo[idx].key
|
|
]))
|
|
for i in range(0, idx + 1):
|
|
connect_nodes(self.nodes[i], idx + 1)
|
|
|
|
def sentinel(self):
|
|
for i in range(1, self.mn_count + 1):
|
|
self.nodes[i].sentinelping("1.1.0")
|
|
|
|
def setup_network(self):
|
|
self.nodes = []
|
|
# create faucet node for collateral and transactions
|
|
self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"]))
|
|
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)
|
|
self.nodes[0] = start_node(0, self.options.tmpdir,
|
|
["-debug", "-sporkkey=cP4EKFyJsHT39LDqgdcB43Y3YXjNyjb5Fuas1GQSeAtjnZWmZEQK"])
|
|
self.create_masternodes()
|
|
# create connected simple nodes
|
|
for i in range(0, self.num_nodes - self.mn_count - 1):
|
|
self.create_simple_node()
|
|
# feed the sender with some balance
|
|
sender_addr = self.nodes[self.sender_idx].getnewaddress()
|
|
self.nodes[0].sendtoaddress(sender_addr, 1)
|
|
# make sender funds mature for InstantSend
|
|
for i in range(0, 2):
|
|
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)
|
|
#self.sentinel()
|
|
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):
|
|
# wait for instantsend locks
|
|
start = time()
|
|
locked = False
|
|
while True:
|
|
is_trx = self.nodes[0].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):
|
|
receiver_addr = self.nodes[self.receiver_idx].getnewaddress()
|
|
txid = self.nodes[0].instantsendtoaddress(receiver_addr, 1.0)
|
|
return self.check_IX_lock(txid)
|
|
|
|
|
|
# sends simple trx, it should become IX if autolocks are allowed
|
|
def send_simple_tx(self):
|
|
raw_tx = self.create_raw_trx(self.nodes[0], self.nodes[self.receiver_idx], 1.0, 1, 4)
|
|
txid = self.nodes[0].sendrawtransaction(raw_tx['hex'])
|
|
self.sync_all()
|
|
return self.check_IX_lock(txid)
|
|
|
|
# sends complex trx, it should never become IX
|
|
def send_complex_tx(self):
|
|
raw_tx = self.create_raw_trx(self.nodes[0], self.nodes[self.receiver_idx], 1.0, 5, 100)
|
|
txid = self.nodes[0].sendrawtransaction(raw_tx['hex'])
|
|
self.sync_all()
|
|
return self.check_IX_lock(txid)
|
|
|
|
def run_test(self):
|
|
self.enforce_masternode_payments() # required for bip9 activation
|
|
assert(self.get_autoix_bip9_status() == 'defined')
|
|
assert(not self.get_autoix_spork_state())
|
|
|
|
assert(self.send_regular_IX())
|
|
assert(not self.send_simple_tx())
|
|
assert(not self.send_complex_tx())
|
|
|
|
self.activate_autoix_bip9()
|
|
self.set_autoix_spork_state(True)
|
|
|
|
assert(self.get_autoix_bip9_status() == 'active')
|
|
assert(self.get_autoix_spork_state())
|
|
|
|
assert(self.send_regular_IX())
|
|
assert(self.send_simple_tx())
|
|
assert(not self.send_complex_tx())
|
|
|
|
if __name__ == '__main__':
|
|
AutoInstantSendTest().main()
|