Changeset View
Standalone View
contrib/devtools/generate_bestchainparams.py
- This file was added.
Property | Old Value | New Value |
---|---|---|
File Mode | null | 100755 |
#!/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 json | |||||
import re | |||||
import subprocess | |||||
import sys | |||||
class Chain(Enum): | |||||
MainNet = "MainNet" | |||||
TestNet = "TestNet" | |||||
def call_rpc_shell(cli, args): | |||||
shell_call = '{} {}'.format(cli, args) | |||||
deadalnix: This is how you create a shell injection attack. | |||||
p = subprocess.Popen(shell_call, shell=True, | |||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |||||
stdout, stderr = p.communicate() | |||||
exit_code = p.wait() | |||||
return shell_call, exit_code, stdout.decode('utf-8'), stderr.decode('utf-8') | |||||
def exit_code_check(shell_call, exit_code, stdout, stderr): | |||||
if exit_code != 0: | |||||
raise Exception("Error calling '{}'\nExit code: {}\nstdout:\n{}\nstderr:\n{}".format( | |||||
shell_call, exit_code, stdout, stderr)) | |||||
return stdout | |||||
def make_bitcoincli_caller(cli): | |||||
return lambda args: call_rpc_shell(cli, args) | |||||
def parse_json(s): | |||||
try: | |||||
return json.loads(s) | |||||
except: | |||||
raise Exception( | |||||
"Error while parsing JSON:\n{}\nInput string:\n{}".format(sys.exc_info(), s)) | |||||
def get_best_chainparams(rpc_caller, chain, block): | |||||
# Fetch initial chain info | |||||
chaininfo = parse_json(rpc_caller('getblockchaininfo')) | |||||
# Make sure the node is on the expected chain | |||||
currentChain = chaininfo['chain'] | |||||
if (chain == Chain.MainNet and currentChain != "main") or (chain == Chain.TestNet and currentChain != "test"): | |||||
raise Exception("The chain network specified did not match the one given by the host node!\nYou requested: {}\nHost node is serving: {}".format( | |||||
chain, currentChain)) | |||||
# 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 -= 2 | |||||
else: | |||||
block -= 2000 | |||||
deadalnixUnsubmitted Not Done Inline ActionsThat seems to be the actual parameter you'd want to configure from the command line rather than the block itself. Maybe the block itself is useful as a parameter, but from an automation standpoint, this is where it is at. For instance, 2 may be enough when you do it manually, and, by the time you put the release out you can check no reorg occured, but if the goal is to automate, is 2 really enough of a margin? deadalnix: That seems to be the actual parameter you'd want to configure from the command line rather than… | |||||
jasonbcoxAuthorUnsubmitted Done Inline ActionsMy thought process on this was: If you're running the script manually and want to choose a block, just pick the block. Otherwise, running with defaults or as an automated process has no reason to change how many blocks from the tip is selected. The conclusion is that configuring this '# of blocks from the tip' value is not useful. jasonbcox: My thought process on this was: If you're running the script manually and want to choose a… | |||||
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 {}'.format(block)).rstrip() | |||||
else: | |||||
raise Exception("Block hash is not a valid block hash or height.") | |||||
# Fetch block chain work | |||||
blockheader = parse_json(rpc_caller('getblockheader {}'.format(block))) | |||||
chainwork = blockheader['chainwork'] | |||||
deadalnixUnsubmitted Not Done Inline ActionsReusing the chainwork from anything but the tip makes no sense whatsoever. If the tip end up being reorged, it'll be by a block that has more work anyway so the value isn't invalidated. deadalnix: Reusing the chainwork from anything but the tip makes no sense whatsoever. If the tip end up… | |||||
jasonbcoxAuthorUnsubmitted Done Inline ActionsGood point. There's no reason for the assumevalid and chainwork to be tightly coupled. jasonbcox: Good point. There's no reason for the assumevalid and chainwork to be tightly coupled. | |||||
if not re.match('^[0-9a-z]{64}$', chainwork): | |||||
raise Exception("Chain work is not a valid uint256 hex value.") | |||||
return (chainwork, block) | |||||
def main(args, make_rpc_caller): | |||||
parser = argparse.ArgumentParser(description=( | |||||
"Generate a chainparams best chain header file." | |||||
"Prerequisites: Two bitcoind nodes running, mainnet and testnet.\n\n"), | |||||
formatter_class=argparse.RawTextHelpFormatter) | |||||
parser.add_argument('--cli', '-c', default="./bitcoin-cli", | |||||
help="The path to the bitcoin-cli binary.\nDefault: ./bitcoin-cli") | |||||
parser.add_argument('--mainnetblock', '-m', | |||||
help="The block hash or height to use for fetching MainNet chainparams.\n" | |||||
"Default: 2 blocks from the chain tip.") | |||||
parser.add_argument('--testnetblock', '-t', | |||||
help="The block hash or height to use for fetching TestNet chainparams.\n" | |||||
"Default: 2000 blocks from the chain tip.") | |||||
args = parser.parse_args(args) | |||||
chains = {} | |||||
chains[Chain.MainNet] = args.mainnetblock | |||||
chains[Chain.TestNet] = args.testnetblock | |||||
params = {} | |||||
for chain, block in chains.items(): | |||||
cli = args.cli | |||||
if not cli: | |||||
cli = './bitcoin-cli' | |||||
if chain == Chain.TestNet: | |||||
cli += " --testnet" | |||||
# Wrap rpc caller with error checker | |||||
def rpc_caller(args): return exit_code_check( | |||||
*(make_rpc_caller(cli))(args)) | |||||
params[chain] = get_best_chainparams(rpc_caller, chain, block) | |||||
output = "" | |||||
output += "// Copyright (c) 2019 The Bitcoin developers\n" | |||||
output += "// Distributed under the MIT software license, see the accompanying\n" | |||||
output += "// file COPYING or http://www.opensource.org/licenses/mit-license.php.\n" | |||||
output += "\n" | |||||
output += "#ifndef BITCOIN_CHAINPARAMSBESTCHAIN_H\n" | |||||
output += "#define BITCOIN_CHAINPARAMSBESTCHAIN_H\n" | |||||
output += "/**\n" | |||||
output += " * Best block hash and chain work chain params for each tracked chain.\n" | |||||
output += " * @""generated by contrib/devtools/generate-bestchainparams.sh\n" | |||||
output += " */\n" | |||||
output += "\n" | |||||
output += "#include <uint256.h>\n" | |||||
output += "\n" | |||||
output += "namespace ChainParamsBestChain {\n" | |||||
for chain, (chainwork, blockhash) in params.items(): | |||||
output += " static const uint256 {}MinimumChainWork = uint256S(\"{}\");\n".format( | |||||
chain.name, chainwork) | |||||
output += " static const uint256 {}DefaultAssumeValid = uint256S(\"{}\");\n".format( | |||||
chain.name, blockhash) | |||||
output += "}\n" | |||||
output += "\n" | |||||
output += "#endif // BITCOIN_CHAINPARAMSBESTCHAIN_H" | |||||
return output | |||||
if __name__ == "__main__": | |||||
print(main(sys.argv[1:], make_bitcoincli_caller)) |
This is how you create a shell injection attack.