# Copyright (C) 2017 chainside srl # # This file is part of the btcpy package. # # It is subject to the license terms in the LICENSE.md file found in the top-level # directory of this distribution. # # No part of btcpy, including this file, may be copied, modified, # propagated, or distributed except according to the terms contained in the # LICENSE.md file. import subprocess import errno from time import sleep import os from shutil import copyfile, rmtree from bitcoin.rpc import RawProxy, JSONRPCError class NodesAlreadyConnected(Exception): pass class RegtestRunning(Exception): pass class SendCommandError(Exception): pass class TxVerifyError(Exception): pass class Manager(object): conf_path = './bitcoin.conf' base_port = 18300 base_rpcport = 18400 user = 'user' password = 'pwd' host = '127.0.0.1' def __init__(self, path='.'): self.nodes_number = 0 self.nodes = {} self.regtest_path = path def generate_nodes(self, num): for n in range(num): self.gen_node(n) def gen_node(self, id_): dst_dir = '{}/node_{}'.format(self.regtest_path, id_) try: os.makedirs(dst_dir) except OSError as exception: if exception.errno != errno.EEXIST: raise default_btc_conf = '{}/{}'.format(os.path.dirname(os.path.realpath(__file__)), Manager.conf_path) copyfile(default_btc_conf, '{}/{}'.format(dst_dir, 'bitcoin.conf')) ip_port = Manager.base_port + id_ rcp_port = Manager.base_rpcport + id_ rpc_user = Manager.user rpc_password = Manager.password with open('{}/{}'.format(dst_dir, 'bitcoin.conf'), 'a') as config_file: config_file.write("rpcuser={}\n".format(rpc_user)) config_file.write("rpcpassword={}\n".format(rpc_password)) config_file.write("port={}\n".format(ip_port)) config_file.write("rpcport={}\n".format(rcp_port)) self.nodes[id_] = None def start_nodes(self): for node in self.nodes: data_dir = os.path.abspath('{}/node_{}'.format(self.regtest_path, node)) conf = os.path.abspath('{}/bitcoin.conf'.format(data_dir)) cmd = ['bitcoind', '-conf={}'.format(conf), '-datadir={}'.format(data_dir), '-maxtxfee=1000', '-debug=1', '-prematurewitness', '-zmqpubrawblock=tcp://127.0.0.1:28332'] # cmd = ['/home/rael/bitcoin/src/bitcoind', '-conf={}'.format(conf), '-datadir={}'.format(data_dir), # '-maxtxfee=100', '-debug=1', '-prematurewitness'] subprocess.call(cmd) # , stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) self.nodes[node] = {'user': Manager.user, 'password': Manager.password, 'host': Manager.host, 'port': Manager.base_port + node, 'rpcport': Manager.base_rpcport + node} sleep(3) for n in self.nodes: for m in self.nodes: if m != n: self.send_rpc_cmd(['addnode', '{}:{}'.format(Manager.host, self.nodes[m]['port']), 'onetry'], n) def stop_nodes(self): self.send_rpc_cmd(['stop'], *[n for n in self.nodes]) def teardown(self): from os.path import abspath self.stop_nodes() for n in self.nodes: rmtree('{}/node_{}'.format(abspath(self.regtest_path), n)) def send_rpc_cmd(self, rpc_cmd, *nodes): # print('Sending rpc command: {}'.format(' '.join(rpc_cmd))) proxies = {} for n in nodes: proxies[n] = RawProxy('http://{user}:{password}@{host}:{rpcport}'.format(**self.nodes[n])) # try convert rpc params from strings to ints for idx, val in enumerate(rpc_cmd): try: rpc_cmd[idx] = int(val) except ValueError: if val in {'False', 'false'}: rpc_cmd[idx] = False elif val in {'True', 'true'}: rpc_cmd[idx] = True for node in nodes: retry = 1 while True: try: cmd, *args = rpc_cmd # print("command:", rpc_cmd) output = getattr(proxies[int(node)], cmd)(*args) # print(output) return output except JSONRPCError as e: retry += 1 if e.error['code'] == -26: raise TxVerifyError('Error during tx validation: {}'.format(e.error['message'])) if e.error['code'] != -28 or retry > 3: raise SendCommandError("Error trying to send command to bitcoincli, " "error code: {}, message: {}".format(e.error['code'], e.error['message'])) sleep(3 * (retry*2))