diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 372eb104b14dd..e248d80859fc0 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -116,6 +116,8 @@ 'p2p-fullblocktest.py', # NOTE: needs dash_hash to pass 'fundrawtransaction.py', 'fundrawtransaction-hd.py', + 'p2p-autoinstantsend.py', + 'autoix-mempool.py', # vv Tests less than 2m vv 'p2p-instantsend.py', 'wallet.py', diff --git a/qa/rpc-tests/autoix-mempool.py b/qa/rpc-tests/autoix-mempool.py new file mode 100755 index 0000000000000..178ef1f2dfbf2 --- /dev/null +++ b/qa/rpc-tests/autoix-mempool.py @@ -0,0 +1,306 @@ +#!/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() diff --git a/qa/rpc-tests/p2p-autoinstantsend.py b/qa/rpc-tests/p2p-autoinstantsend.py new file mode 100755 index 0000000000000..fb3ca47d3c2bf --- /dev/null +++ b/qa/rpc-tests/p2p-autoinstantsend.py @@ -0,0 +1,300 @@ +#!/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() diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b7edaa7bda0fc..d2a9da6db1688 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -174,6 +174,13 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nWindowSize = 4032; consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nThreshold = 3226; // 80% of 4032 + // Deployment of InstantSend autolocks + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].bit = 4; + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nStartTime = 1533945600; // Aug 11th, 2018 + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nTimeout = 1565481600; // Aug 11th, 2019 + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nWindowSize = 4032; + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nThreshold = 3226; // 80% of 4032 + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000081021b74f9f47bbd7bc"); // 888900 @@ -327,6 +334,13 @@ class CTestNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nWindowSize = 100; consensus.vDeployments[Consensus::DEPLOYMENT_BIP147].nThreshold = 50; // 50% of 100 + // Deployment of InstantSend autolocks + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].bit = 4; + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nStartTime = 1532476800; // Jul 25th, 2018 + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nTimeout = 1564012800; // Jul 25th, 2019 + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nWindowSize = 100; + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nThreshold = 50; // 50% of 100 + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000003be69c34b1244f"); // 143200 @@ -464,6 +478,13 @@ class CDevNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nWindowSize = 100; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nThreshold = 50; // 50% of 100 + // Deployment of InstantSend autolocks + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].bit = 4; + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nStartTime = 1535752800; // Sep 1st, 2018 + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nTimeout = 1567288800; // Sep 1st, 2019 + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nWindowSize = 100; + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nThreshold = 50; // 50% of 100 + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000000000000000"); @@ -583,6 +604,10 @@ class CRegTestParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].bit = 3; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_DIP0003].nTimeout = 999999999999ULL; + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].bit = 4; + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nStartTime = 0; + consensus.vDeployments[Consensus::DEPLOYMENT_ISAUTOLOCKS].nTimeout = 999999999999ULL; + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); diff --git a/src/consensus/params.h b/src/consensus/params.h index 524d094fd8ab7..f602bc029ec6d 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -19,6 +19,7 @@ enum DeploymentPos DEPLOYMENT_DIP0001, // Deployment of DIP0001 and lower transaction fees. DEPLOYMENT_BIP147, // Deployment of BIP147 (NULLDUMMY) DEPLOYMENT_DIP0003, // Deployment of DIP0002 and DIP0003 (txv3 and deterministic MN lists) + DEPLOYMENT_ISAUTOLOCKS, // Deployment of automatic IS locks for simple transactions // NOTE: Also add new deployments to VersionBitsDeploymentInfo in versionbits.cpp MAX_VERSION_BITS_DEPLOYMENTS }; diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index f7c1d2bc04c3b..5e7fbcef5acc9 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -44,6 +44,9 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con // Update global DIP0001 activation status fDIP0001ActiveAtTip = pindexNew->nHeight >= Params().GetConsensus().DIP0001Height; fDIP0003ActiveAtTip = (VersionBitsState(pindexNew->pprev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003, versionbitscache) == THRESHOLD_ACTIVE); + // update instantsend autolock activation flag + instantsend.isAutoLockBip9Active = + (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_ISAUTOLOCKS) == THRESHOLD_ACTIVE); if (fInitialDownload) return; diff --git a/src/instantx.cpp b/src/instantx.cpp index fbf30ac668e74..be4148b3995a5 100644 --- a/src/instantx.cpp +++ b/src/instantx.cpp @@ -35,6 +35,9 @@ extern CTxMemPool mempool; bool fEnableInstantSend = true; int nCompleteTXLocks; +std::atomic CInstantSend::isAutoLockBip9Active{false}; +const double CInstantSend::AUTO_IX_MEMPOOL_THRESHOLD = 0.1; + CInstantSend instantsend; const std::string CInstantSend::SERIALIZATION_VERSION_STRING = "CInstantSend-Version-1"; @@ -915,6 +918,15 @@ std::string CInstantSend::ToString() const return strprintf("Lock Candidates: %llu, Votes %llu", mapTxLockCandidates.size(), mapTxLockVotes.size()); } +bool CInstantSend::CanAutoLock() +{ + if(!isAutoLockBip9Active) + return false; + if(!sporkManager.IsSporkActive(SPORK_16_INSTANTSEND_AUTOLOCKS)) + return false; + return (mempool.UsedMemoryShare() < AUTO_IX_MEMPOOL_THRESHOLD); +} + // // CTxLockRequest // @@ -976,6 +988,8 @@ bool CTxLockRequest::IsValid() const CAmount CTxLockRequest::GetMinFee() const { + if(IsSimple()) + return CAmount(); CAmount nMinFee = MIN_FEE; return std::max(nMinFee, CAmount(tx->vin.size() * nMinFee)); } @@ -985,6 +999,11 @@ int CTxLockRequest::GetMaxSignatures() const return tx->vin.size() * COutPointLock::SIGNATURES_TOTAL; } +bool CTxLockRequest::IsSimple() const +{ + return (tx->vin.size() <= MAX_INPUTS_FOR_AUTO_IX); +} + // // CTxLockVote // diff --git a/src/instantx.h b/src/instantx.h index 0746f5b4f4473..72361c66212b5 100644 --- a/src/instantx.h +++ b/src/instantx.h @@ -28,6 +28,8 @@ extern CInstantSend instantsend; static const int MIN_INSTANTSEND_PROTO_VERSION = 70210; +static const int MIN_INSTANTSEND_WITHOUT_FEE_PROTO_VERSION = 70211; + /// For how long we are going to accept votes/locks /// after we saw the first one for a specific transaction static const int INSTANTSEND_LOCK_TIMEOUT_SECONDS = 15; @@ -45,6 +47,9 @@ class CInstantSend { private: static const std::string SERIALIZATION_VERSION_STRING; + /// Automatic locks of "simple" transactions are only allowed + /// when mempool usage is lower than this threshold + static const double AUTO_IX_MEMPOOL_THRESHOLD; // Keep track of current block height int nCachedBlockHeight; @@ -148,6 +153,12 @@ class CInstantSend std::string ToString() const; void DoMaintenance() { CheckAndRemove(); } + + /// checks if we can automatically lock "simple" transactions + static bool CanAutoLock(); + + /// flag of the AutoLock Bip9 activation + static std::atomic isAutoLockBip9Active; }; /** @@ -157,6 +168,9 @@ class CTxLockRequest { private: static const CAmount MIN_FEE = 0.0001 * COIN; + /// If transaction has less or equal inputs than MAX_INPUTS_FOR_AUTO_IX, + /// it will be automatically locked + static const int MAX_INPUTS_FOR_AUTO_IX = 4; public: /// Warn for a large number of inputs to an IS tx - fees could be substantial @@ -167,6 +181,7 @@ class CTxLockRequest CTxLockRequest() : tx(MakeTransactionRef()) {} CTxLockRequest(const CTransaction& _tx) : tx(MakeTransactionRef(_tx)) {}; + CTxLockRequest(const CTransactionRef& _tx) : tx(_tx) {}; ADD_SERIALIZE_METHODS; @@ -179,6 +194,9 @@ class CTxLockRequest CAmount GetMinFee() const; int GetMaxSignatures() const; + // checks if related transaction is "simple" to lock it automatically + bool IsSimple() const; + const uint256 &GetHash() const { return tx->GetHash(); } diff --git a/src/net_processing.cpp b/src/net_processing.cpp index eb62b815d43f2..e5bf5541c32f3 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1942,10 +1942,13 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr CTxLockRequest txLockRequest; CDarksendBroadcastTx dstx; int nInvType = MSG_TX; + bool fCanAutoLock = false; // Read data and assign inv type if(strCommand == NetMsgType::TX) { vRecv >> ptx; + txLockRequest = CTxLockRequest(ptx); + fCanAutoLock = CInstantSend::CanAutoLock() && txLockRequest.IsSimple(); } else if(strCommand == NetMsgType::TXLOCKREQUEST) { vRecv >> txLockRequest; ptx = txLockRequest.tx; @@ -1962,10 +1965,16 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr pfrom->setAskFor.erase(inv.hash); // Process custom logic, no matter if tx will be accepted to mempool later or not - if (strCommand == NetMsgType::TXLOCKREQUEST) { + if (strCommand == NetMsgType::TXLOCKREQUEST || fCanAutoLock) { if(!instantsend.ProcessTxLockRequest(txLockRequest, connman)) { LogPrint("instantsend", "TXLOCKREQUEST -- failed %s\n", txLockRequest.GetHash().ToString()); - return false; + // Should not really happen for "fCanAutoLock == true" but just in case: + if (!fCanAutoLock) { + // Fail only for "true" IS here + return false; + } + // Fallback for normal txes to process as usual + fCanAutoLock = false; } } else if (strCommand == NetMsgType::DSTX) { uint256 hashTx = tx.GetHash(); @@ -2011,7 +2020,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr LogPrintf("DSTX -- Masternode transaction accepted, txid=%s, peer=%d\n", tx.GetHash().ToString(), pfrom->id); CPrivateSend::AddDSTX(dstx); - } else if (strCommand == NetMsgType::TXLOCKREQUEST) { + } else if (strCommand == NetMsgType::TXLOCKREQUEST || fCanAutoLock) { LogPrintf("TXLOCKREQUEST -- Transaction Lock Request accepted, txid=%s, peer=%d\n", tx.GetHash().ToString(), pfrom->id); instantsend.AcceptLockRequest(txLockRequest); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 582e47e353f71..14b5dc00600e4 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1287,6 +1287,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) BIP9SoftForkDescPushBack(bip9_softforks, "dip0001", consensusParams, Consensus::DEPLOYMENT_DIP0001); BIP9SoftForkDescPushBack(bip9_softforks, "dip0003", consensusParams, Consensus::DEPLOYMENT_DIP0003); BIP9SoftForkDescPushBack(bip9_softforks, "bip147", consensusParams, Consensus::DEPLOYMENT_BIP147); + BIP9SoftForkDescPushBack(bip9_softforks, "autoix", consensusParams, Consensus::DEPLOYMENT_ISAUTOLOCKS); obj.push_back(Pair("softforks", softforks)); obj.push_back(Pair("bip9_softforks", bip9_softforks)); diff --git a/src/spork.cpp b/src/spork.cpp index 1d096fa935891..ceca957771d0d 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -26,6 +26,7 @@ std::map mapSporkDefaults = { {SPORK_12_RECONSIDER_BLOCKS, 0}, // 0 BLOCKS {SPORK_14_REQUIRE_SENTINEL_FLAG, 4070908800ULL}, // OFF {SPORK_15_DETERMINISTIC_MNS_ENABLED, 4070908800ULL}, // OFF + {SPORK_16_INSTANTSEND_AUTOLOCKS, 4070908800ULL}, // OFF }; void CSporkManager::Clear() @@ -184,6 +185,7 @@ int CSporkManager::GetSporkIDByName(const std::string& strName) if (strName == "SPORK_12_RECONSIDER_BLOCKS") return SPORK_12_RECONSIDER_BLOCKS; if (strName == "SPORK_14_REQUIRE_SENTINEL_FLAG") return SPORK_14_REQUIRE_SENTINEL_FLAG; if (strName == "SPORK_15_DETERMINISTIC_MNS_ENABLED") return SPORK_15_DETERMINISTIC_MNS_ENABLED; + if (strName == "SPORK_16_INSTANTSEND_AUTOLOCKS") return SPORK_16_INSTANTSEND_AUTOLOCKS; LogPrint("spork", "CSporkManager::GetSporkIDByName -- Unknown Spork name '%s'\n", strName); return -1; @@ -202,6 +204,7 @@ std::string CSporkManager::GetSporkNameByID(int nSporkID) case SPORK_12_RECONSIDER_BLOCKS: return "SPORK_12_RECONSIDER_BLOCKS"; case SPORK_14_REQUIRE_SENTINEL_FLAG: return "SPORK_14_REQUIRE_SENTINEL_FLAG"; case SPORK_15_DETERMINISTIC_MNS_ENABLED: return "SPORK_15_DETERMINISTIC_MNS_ENABLED"; + case SPORK_16_INSTANTSEND_AUTOLOCKS: return "SPORK_16_INSTANTSEND_AUTOLOCKS"; default: LogPrint("spork", "CSporkManager::GetSporkNameByID -- Unknown Spork ID %d\n", nSporkID); return "Unknown"; diff --git a/src/spork.h b/src/spork.h index 4ccd619d12318..0d05a8f5115e5 100644 --- a/src/spork.h +++ b/src/spork.h @@ -27,9 +27,10 @@ static const int SPORK_10_MASTERNODE_PAY_UPDATED_NODES = 10009; static const int SPORK_12_RECONSIDER_BLOCKS = 10011; static const int SPORK_14_REQUIRE_SENTINEL_FLAG = 10013; static const int SPORK_15_DETERMINISTIC_MNS_ENABLED = 10014; +static const int SPORK_16_INSTANTSEND_AUTOLOCKS = 10015; static const int SPORK_START = SPORK_2_INSTANTSEND_ENABLED; -static const int SPORK_END = SPORK_15_DETERMINISTIC_MNS_ENABLED; +static const int SPORK_END = SPORK_16_INSTANTSEND_AUTOLOCKS; extern std::map mapSporkDefaults; extern CSporkManager sporkManager; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index bca9ad3d50cd6..a0eea414685aa 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1269,6 +1269,16 @@ size_t CTxMemPool::DynamicMemoryUsage() const { return memusage::MallocUsage(sizeof(CTxMemPoolEntry) + 15 * sizeof(void*)) * mapTx.size() + memusage::DynamicUsage(mapNextTx) + memusage::DynamicUsage(mapDeltas) + memusage::DynamicUsage(mapLinks) + memusage::DynamicUsage(vTxHashes) + cachedInnerUsage; } +double CTxMemPool::UsedMemoryShare() const +{ + // use 1000000 instead of real bytes number in megabyte because of + // this param is calculated in such way in other places (see AppInit + // function in src/init.cpp or mempoolInfoToJSON function in + // src/rpc/blockchain.cpp) + size_t maxmempool = GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000; + return double(DynamicMemoryUsage()) / maxmempool; +} + void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants, MemPoolRemovalReason reason) { AssertLockHeld(cs); UpdateForRemoveFromMempool(stage, updateDescendants); diff --git a/src/txmempool.h b/src/txmempool.h index d1cec34d21c7b..38f4dbcff401d 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -713,6 +713,8 @@ class CTxMemPool bool ReadFeeEstimates(CAutoFile& filein); size_t DynamicMemoryUsage() const; + // returns share of the used memory to maximum allowed memory + double UsedMemoryShare() const; boost::signals2::signal NotifyEntryAdded; boost::signals2::signal NotifyEntryRemoved; diff --git a/src/validation.cpp b/src/validation.cpp index 9723134029564..e4c8a051816c9 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1818,7 +1818,7 @@ int32_t ComputeBlockVersion(const CBlockIndex* pindexPrev, const Consensus::Para // unknown masternode continue; } - if (mnInfo.nProtocolVersion < DIP0001_PROTOCOL_VERSION) { + if (mnInfo.nProtocolVersion < MIN_INSTANTSEND_WITHOUT_FEE_PROTO_VERSION) { // masternode is not upgraded yet continue; } diff --git a/src/versionbits.cpp b/src/versionbits.cpp index f7a5ce1d2bf90..29aa97366d870 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -31,6 +31,11 @@ const struct BIP9DeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION /*.name =*/ "dip0003", /*.gbt_force =*/ true, /*.check_mn_protocol =*/ false, + }, + { + /*.name =*/ "autoix", + /*.gbt_force =*/ true, + /*.check_mn_protocol =*/ true, } }; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 2da41cd6d3b58..53546d8e22202 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1979,7 +1979,8 @@ bool CWalletTx::RelayWalletTransaction(CConnman* connman, const std::string& str uint256 hash = GetHash(); LogPrintf("Relaying wtx %s\n", hash.ToString()); - if (strCommand == NetMsgType::TXLOCKREQUEST) { + if ((strCommand == NetMsgType::TXLOCKREQUEST) || + ((CTxLockRequest(*this).IsSimple()) && CInstantSend::CanAutoLock())) { if (instantsend.ProcessTxLockRequest((CTxLockRequest)*this, *connman)) { instantsend.AcceptLockRequest((CTxLockRequest)*this); } else {