diff --git a/contrib/devtools/chainparams/make_chainparams.py b/contrib/devtools/chainparams/make_chainparams.py index 9017407cf0..1a42b88c60 100755 --- a/contrib/devtools/chainparams/make_chainparams.py +++ b/contrib/devtools/chainparams/make_chainparams.py @@ -1,108 +1,108 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. from enum import Enum import argparse import os.path import re import sys sys.path.append('../../../test/functional/test_framework') from authproxy import AuthServiceProxy # noqa: E402 class Chain(Enum): MainNet = "MAINNET" TestNet = "TESTNET" def get_chainparams(rpc_caller, block): # Fetch initial chain info chaininfo = rpc_caller.getblockchaininfo() if chaininfo['chain'] == 'main': chain = Chain.MainNet else: chain = Chain.TestNet # Use highest valid chainwork. This doesn't need to match the block hash # used by assume valid. chainwork = chaininfo['chainwork'] if not re.match('^[0-9a-z]{64}$', chainwork): raise Exception("Chain work is not a valid uint256 hex value.") # Default to N blocks from the chain tip, depending on which chain we're on if not block: block = chaininfo['blocks'] if chain == Chain.MainNet: block -= 10 else: block -= 2000 block = str(block) if not re.match('^[0-9a-z]{64}$', block): if re.match('^[0-9]*$', block): # Fetch block hash using block height block = rpc_caller.getblockhash(int(block)) else: raise Exception("Block hash is not a valid block hash or height.") # Make sure the block hash is part of the chain. This call with raise an # exception if not. rpc_caller.getblockheader(block) return (chainwork, block) def main(args): (chainwork, blockhash) = get_chainparams(args['rpc'], args['block']) - output = "{}\n{}\n".format(blockhash, chainwork) + output = "{}\n{}".format(blockhash, chainwork) return output if __name__ == "__main__": parser = argparse.ArgumentParser(description=( "Make chainparams file.\n" "Prerequisites: RPC access to a bitcoind node.\n\n"), formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('--address', '-a', default="127.0.0.1:8332", help="Node address for making RPC calls.\n" "The chain (MainNet or TestNet) will be automatically detected.\n" "Default: '127.0.0.1:8332'") parser.add_argument('--block', '-b', help="The block hash or height to use for fetching chainparams.\n" "MainNet default: 10 blocks from the chain tip." "TestNet default: 2000 blocks from the chain tip.") parser.add_argument('--config', '-c', default="~/.bitcoin/bitcoin.conf", help="Path to bitcoin.conf for RPC authentication arguments (rpcuser & rpcpassword).\n" "Default: ~/.bitcoin/bitcoin.conf") args = parser.parse_args() args.config = os.path.expanduser(args.config) # Get user and password from config user = None password = None if os.path.isfile(args.config): with open(args.config, 'r', encoding='utf8') as f: for line in f: if line.startswith("rpcuser="): # Ensure that there is only one rpcuser line assert user is None user = line.split("=")[1].strip("\n") if line.startswith("rpcpassword="): # Ensure that there is only one rpcpassword line assert password is None password = line.split("=")[1].strip("\n") else: raise FileNotFoundError("Missing bitcoin.conf") if user is None: raise ValueError("Config is missing rpcuser") if password is None: raise ValueError("Config is missing rpcpassword") args.rpc = AuthServiceProxy( 'http://{}:{}@{}'.format(user, password, args.address)) output = main(vars(args)) if output: print(output) diff --git a/contrib/devtools/chainparams/test_make_chainparams.py b/contrib/devtools/chainparams/test_make_chainparams.py index 978faab48f..ae89c792ad 100755 --- a/contrib/devtools/chainparams/test_make_chainparams.py +++ b/contrib/devtools/chainparams/test_make_chainparams.py @@ -1,170 +1,170 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. import unittest from make_chainparams import main as GenerateChainParams class MockRPC: def __init__(self, test, chain, numBlocks, expectedBlock, blockHash, chainWork): self.test = test self.chain = chain self.numBlocks = numBlocks self.expectedBlock = expectedBlock self.blockHash = blockHash self.chainWork = chainWork def getblockchaininfo(self): return { "chain": self.chain, "blocks": self.numBlocks, "headers": self.numBlocks, "bestblockhash": "0000000000000000039c96605d7fca74a5e185ea5634198346e9e07fd235b666", "difficulty": 274412074285.6605, "mediantime": 1562168718, "verificationprogress": 0.9999958005632363, "initialblockdownload": False, "chainwork": self.chainWork, "size_on_disk": 952031444, "pruned": True, "pruneheight": 582974, "automatic_pruning": True, "prune_target_size": 1048576000, } def getblockhash(self, block): # Tests should always request the right block height. Even though a # real node will rarely raise an exception for this call, we are # more strict during testing. self.test.assertEqual(block, self.expectedBlock, "Called 'getblockhash {}' when expected was 'getblockhash {}'".format( block, self.expectedBlock)) return self.blockHash def getblockheader(self, blockHash): # Make sure to raise an exception in the same way a real node would # when calling 'getblockheader' on a block hash that is not part of # the chain. self.test.assertEqual(blockHash, self.blockHash, "Called 'getblockheader {}' when expected was 'getblockheader {}'".format( blockHash, self.blockHash)) return { "hash": blockHash, "confirmations": 1, "height": 591463, "version": 536870912, "versionHex": "20000000", "merkleroot": "51c898f034b6c5a5513a7c35912e86d009188311e550bb3096e04afb11f40aba", "time": 1563212034, "mediantime": 1563208994, "nonce": 3501699724, "bits": "18040cd6", "difficulty": 271470800310.0635, "chainwork": "000000000000000000000000000000000000000000f4c5e639fa012518a48a57", "previousblockhash": "00000000000000000307b45e4a6cf8d49e70b9012ea1d72a5ce334a4213f66bd", } class MockFailRPC(MockRPC): # Provides a fail counter to fail after the Nth RPC command def __init__(self, test, chain, numBlocks, expectedBlock, blockHash, chainWork, failCounter): super().__init__(test, chain, numBlocks, expectedBlock, blockHash, chainWork) self.failCounter = failCounter def checkFailCounter(self): self.failCounter -= 1 if self.failCounter < 0: raise Exception("""error code: -99 error message: mock error""") def getblockchaininfo(self): self.checkFailCounter() return super().getblockchaininfo() def getblockhash(self, block): self.checkFailCounter() return super().getblockhash(block) def getblockheader(self, blockHash): self.checkFailCounter() return super().getblockheader(blockHash) def CheckMockFailure(test, args, errorMessage='error code: -99'): with test.assertRaises(Exception) as context: GenerateChainParams(args) test.assertIn(errorMessage, str(context.exception)) class GenerateChainParamsTests(unittest.TestCase): maxDiff = None def setUp(self): self.blockHash1 = '0000000000000000003ef673ae12bc6017481830d37b9c52ce1e79c080e812b8' self.chainWork1 = '000000000000000000000000000000000000000000f2537ccf2e07bbe15e70e1' self.blockHash2 = '0000000000000298a9fa227f0ec32f2b7585f3e64c8b3369e7f8b4fd8ea3d836' self.chainWork2 = '00000000000000000000000000000000000000000000004fdb4795a837f19671' def test_happy_path_mainnet(self): mockRPC = MockRPC(test=self, chain='main', numBlocks=123000, expectedBlock=122990, blockHash=self.blockHash1, chainWork=self.chainWork1) args = { 'rpc': mockRPC, 'block': None, } - self.assertEqual(GenerateChainParams(args), "{}\n{}\n".format( + self.assertEqual(GenerateChainParams(args), "{}\n{}".format( "0000000000000000003ef673ae12bc6017481830d37b9c52ce1e79c080e812b8", "000000000000000000000000000000000000000000f2537ccf2e07bbe15e70e1")) def test_happy_path_testnet(self): mockRPC = MockRPC(test=self, chain='test', numBlocks=234000, expectedBlock=232000, blockHash=self.blockHash1, chainWork=self.chainWork1) args = { 'rpc': mockRPC, 'block': None, } - self.assertEqual(GenerateChainParams(args), "{}\n{}\n".format( + self.assertEqual(GenerateChainParams(args), "{}\n{}".format( "0000000000000000003ef673ae12bc6017481830d37b9c52ce1e79c080e812b8", "000000000000000000000000000000000000000000f2537ccf2e07bbe15e70e1")) def test_specific_block(self): mockRPC = MockRPC(test=self, chain='main', numBlocks=123000, expectedBlock=122990, blockHash=self.blockHash1, chainWork=self.chainWork1) args = { 'rpc': mockRPC, 'block': self.blockHash1, } - self.assertEqual(GenerateChainParams(args), "{}\n{}\n".format( + self.assertEqual(GenerateChainParams(args), "{}\n{}".format( "0000000000000000003ef673ae12bc6017481830d37b9c52ce1e79c080e812b8", "000000000000000000000000000000000000000000f2537ccf2e07bbe15e70e1")) def test_wrong_chain(self): mockRPC = MockRPC(test=self, chain='main', numBlocks=123000, expectedBlock=122990, blockHash=self.blockHash1, chainWork=self.chainWork1) args = { 'rpc': mockRPC, 'block': self.blockHash2, } CheckMockFailure( self, args, "expected was 'getblockheader 0000000000000000003ef673ae12bc6017481830d37b9c52ce1e79c080e812b8'") def test_bitcoin_cli_failures_testnet(self): for chain in ['main', 'test']: expectedBlock = 133990 if chain == 'test': expectedBlock = 132000 for failCounter in range(3): mockFailRPC = MockFailRPC(test=self, chain=chain, numBlocks=134000, expectedBlock=expectedBlock, blockHash=self.blockHash1, chainWork=self.chainWork1, failCounter=failCounter) argsFail = { 'rpc': mockFailRPC, 'block': None, } CheckMockFailure(self, argsFail) unittest.main()