Skip to content

Commit 33decfe

Browse files
authored
refactor: New devgenesis.py (#312)
1 parent 679965e commit 33decfe

File tree

2 files changed

+320
-137
lines changed

2 files changed

+320
-137
lines changed

scripts/devgenesis.py

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import json
5+
import os
6+
import re
7+
import subprocess
8+
import sys
9+
from pathlib import Path
10+
from typing import Any, Dict, List
11+
12+
import bech32
13+
14+
DEFAULT_STAKING_DENOM = "afet"
15+
DEFAULT_HOME_PATH = os.path.expanduser("~") + "/.fetchd"
16+
DEFAULT_VALIDATOR_KEY_NAME = "validator"
17+
FUND_BALANCE = 10**23
18+
DEFAULT_VOTING_PERIOD = "60s"
19+
20+
21+
def parse_commandline():
22+
description = """This script updates an exported genesis from a running chain
23+
to be used to run on a single validator local node.
24+
It will take the first validator and jail all the others
25+
and replace the validator pubkey and the nodekey with the one
26+
found in the node_home_dir folder
27+
28+
if unspecified, node_home_dir default to the ~/.fetchd/ folder.
29+
this folder must exists and contains the files created by the "fetchd init" command.
30+
31+
The updated genesis will be written under node_home_dir/config/genesis.json, allowing
32+
the local chain to be started with:
33+
"""
34+
35+
parser = argparse.ArgumentParser(description=description)
36+
parser.add_argument(
37+
"genesis_export", type=_path, help="The path to the genesis export"
38+
)
39+
parser.add_argument(
40+
"--home_path",
41+
help="The path to the local node data i.e. ~/.fetchd",
42+
default=DEFAULT_HOME_PATH,
43+
)
44+
parser.add_argument(
45+
"--validator_key_name",
46+
help="The name of the local key to use for the validator",
47+
default=DEFAULT_VALIDATOR_KEY_NAME,
48+
)
49+
parser.add_argument(
50+
"--staking_denom", help="The staking denom", default=DEFAULT_STAKING_DENOM
51+
)
52+
parser.add_argument("--chain_id", help="New chain ID to be set", default=None)
53+
parser.add_argument(
54+
"--voting_period",
55+
help="The new voting period to be set",
56+
default=DEFAULT_VOTING_PERIOD,
57+
)
58+
59+
return parser.parse_args()
60+
61+
62+
def _path(text: str) -> str:
63+
return os.path.abspath(text)
64+
65+
66+
def _convert_to_valoper(address):
67+
hrp, data = bech32.bech32_decode(address)
68+
if hrp != "fetch":
69+
print("Invalid address, expected normal fetch address")
70+
sys.exit(1)
71+
72+
return bech32.bech32_encode("fetchvaloper", data)
73+
74+
75+
def _ensure_account(genesis, address):
76+
for account in genesis["app_state"]["auth"]["accounts"]:
77+
if "address" in account and account["address"] == address:
78+
return
79+
80+
# Add new account to auth
81+
last_account_number = int(
82+
genesis["app_state"]["auth"]["accounts"][-1]["account_number"]
83+
)
84+
85+
# Ensure unique account number
86+
new_account = {
87+
"@type": "/cosmos.auth.v1beta1.BaseAccount",
88+
"account_number": str(last_account_number + 1),
89+
"address": address,
90+
"pub_key": None,
91+
"sequence": "0",
92+
}
93+
genesis["app_state"]["auth"]["accounts"].append(new_account)
94+
return genesis
95+
96+
97+
def _set_balance(genesis, address, new_balance, denom):
98+
account_found = False
99+
for balance in genesis["app_state"]["bank"]["balances"]:
100+
if balance["address"] == address:
101+
for amount in balance["coins"]:
102+
if amount["denom"] == denom:
103+
amount["amount"] = str(new_balance)
104+
account_found = True
105+
106+
if not account_found:
107+
new_balance_entry = {
108+
"address": address,
109+
"coins": [{"amount": str(new_balance), "denom": denom}],
110+
}
111+
genesis["app_state"]["bank"]["balances"].append(new_balance_entry)
112+
return genesis
113+
114+
115+
def _get_balance(genesis, address, denom):
116+
amount = 0
117+
for balance in genesis["app_state"]["bank"]["balances"]:
118+
if balance["address"] == address:
119+
for amount in balance["coins"]:
120+
if amount["denom"] == denom:
121+
amount = int(amount["amount"])
122+
break
123+
if amount is not 0:
124+
break
125+
return amount
126+
127+
128+
def main():
129+
args = parse_commandline()
130+
131+
print(" Genesis Export:", args.genesis_export)
132+
print(" Fetchd Home Path:", args.home_path)
133+
print("Validator Key Name:", args.validator_key_name)
134+
135+
# load up the local validator key
136+
local_validator_key_path = os.path.join(
137+
args.home_path, "config", "priv_validator_key.json"
138+
)
139+
with open(local_validator_key_path, "r") as input_file:
140+
local_validator_key = json.load(input_file)
141+
142+
# extract the tendermint addresses
143+
cmd = ["fetchd", "--home", args.home_path, "tendermint", "show-address"]
144+
validator_address = subprocess.check_output(cmd).decode().strip()
145+
validator_pubkey = local_validator_key["pub_key"]["value"]
146+
validator_hexaddr = local_validator_key["address"]
147+
148+
# extract the address for the local validator key
149+
cmd = [
150+
"fetchd",
151+
"--home",
152+
args.home_path,
153+
"keys",
154+
"show",
155+
args.validator_key_name,
156+
"--output",
157+
"json",
158+
]
159+
key_data = json.loads(subprocess.check_output(cmd).decode())
160+
161+
if key_data["type"] != "local":
162+
print("Unable to use non-local key type")
163+
sys.exit(1)
164+
165+
# extract the local address and convert into a valid validator operator address
166+
validator_operator_base_address = key_data["address"]
167+
validator_operator_address = _convert_to_valoper(validator_operator_base_address)
168+
print(f" {validator_operator_base_address}")
169+
print(validator_operator_address)
170+
171+
# load the genesis up
172+
print("reading genesis export...")
173+
with open(args.genesis_export, "r") as export_file:
174+
genesis = json.load(export_file)
175+
print("reading genesis export...complete")
176+
177+
val_infos = genesis["app_state"]["staking"]["validators"][0]
178+
if not val_infos:
179+
print("Genesis file does not contain any validators")
180+
sys.exit(1)
181+
182+
target_validator_operator_address = val_infos["operator_address"]
183+
target_validator_public_key = val_infos["consensus_pubkey"]["key"]
184+
val_tokens = int(val_infos["tokens"])
185+
val_power = int(val_tokens / (10**18))
186+
187+
# Replace selected validator by current node one
188+
print(f"Replacing validator {target_validator_operator_address}...")
189+
190+
val_addr = None
191+
genesis["app_state"]["staking"]["validators"][0]["consensus_pubkey"][
192+
"key"
193+
] = validator_pubkey
194+
for val in genesis["validators"]:
195+
if val["pub_key"]["value"] == target_validator_public_key:
196+
val["pub_key"]["value"] = validator_pubkey
197+
val_addr = val["address"]
198+
break
199+
assert val_addr is not None, "Validator not found in genesis"
200+
201+
genesis_dump = json.dumps(genesis)
202+
genesis_dump = re.sub(val_addr, validator_hexaddr, genesis_dump)
203+
genesis_dump = re.sub(
204+
target_validator_operator_address, validator_operator_address, genesis_dump
205+
)
206+
genesis = json.loads(genesis_dump)
207+
208+
# Set .app_state.slashing.signing_infos to contain only our validator signing infos
209+
print("Updating signing infos...")
210+
genesis["app_state"]["slashing"]["signing_infos"] = [
211+
{
212+
"address": validator_address,
213+
"validator_signing_info": {
214+
"address": validator_address,
215+
"index_offset": "0",
216+
"jailed_until": "1970-01-01T00:00:00Z",
217+
"missed_blocks_counter": "0",
218+
"start_height": "0",
219+
"tombstoned": False,
220+
},
221+
}
222+
]
223+
224+
# Find the bonded and not bonded token pools
225+
print("Finding bonded and not bonded token pools...")
226+
227+
bonded_pool_address = None
228+
not_bonded_pool_address = None
229+
for account in genesis["app_state"]["auth"]["accounts"]:
230+
if "name" in account:
231+
if account["name"] == "bonded_tokens_pool":
232+
bonded_pool_address = account["base_account"]["address"]
233+
elif account["name"] == "not_bonded_tokens_pool":
234+
not_bonded_pool_address = account["base_account"]["address"]
235+
236+
if bonded_pool_address and not_bonded_pool_address:
237+
break
238+
239+
# Update bonded and not bonded pool values to make invariant checks happy
240+
print("Updating bonded and not bonded token pool values...")
241+
242+
# Get current bonded and not bonded tokens
243+
bonded_tokens = _get_balance(genesis, bonded_pool_address, args.staking_denom)
244+
not_bonded_tokens = _get_balance(
245+
genesis, not_bonded_pool_address, args.staking_denom
246+
)
247+
248+
new_not_bonded_tokens = not_bonded_tokens + bonded_tokens - val_tokens
249+
250+
# Update bonded pool and not bonded pool balances
251+
_set_balance(genesis, bonded_pool_address, val_tokens, args.staking_denom)
252+
_set_balance(
253+
genesis, not_bonded_pool_address, new_not_bonded_tokens, args.staking_denom
254+
)
255+
256+
# Create new account and fund it
257+
print(
258+
f"Creating new funded account for local validator {validator_operator_base_address}..."
259+
)
260+
261+
# Add new balance to bank
262+
genesis = _set_balance(
263+
genesis, validator_operator_base_address, FUND_BALANCE, args.staking_denom
264+
)
265+
266+
# Add new account to auth if not already there
267+
genesis = _ensure_account(genesis, validator_operator_base_address)
268+
269+
# Update total supply of staking denom with new funds added
270+
for supply in genesis["app_state"]["bank"]["supply"]:
271+
if supply["denom"] == args.staking_denom:
272+
supply["amount"] = str(int(supply["amount"]) + FUND_BALANCE)
273+
274+
# Remove all .validators but the one we work with
275+
print("Removing other validators from initchain...")
276+
genesis["validators"] = [
277+
val for val in genesis["validators"] if val["address"] == validator_hexaddr
278+
]
279+
280+
# Set .app_state.staking.last_validator_powers to contain only our validator
281+
print("Updating last voting power...")
282+
genesis["app_state"]["staking"]["last_validator_powers"] = [
283+
{"address": validator_operator_address, "power": str(val_power)}
284+
]
285+
286+
# Jail everyone but our validator
287+
print("Jail other validators...")
288+
for validator in genesis["app_state"]["staking"]["validators"]:
289+
if validator["operator_address"] != validator_operator_address:
290+
validator["status"] = "BOND_STATUS_UNBONDING"
291+
validator["jailed"] = True
292+
293+
if "max_wasm_code_size" in genesis["app_state"]["wasm"]["params"]:
294+
print("Removing max_wasm_code_size...")
295+
del genesis["app_state"]["wasm"]["params"]["max_wasm_code_size"]
296+
297+
# Set voting period
298+
print(f"Setting voting period to {args.voting_period}...")
299+
genesis["app_state"]["gov"]["voting_params"]["voting_period"] = args.voting_period
300+
301+
# Update the chain id if provided
302+
if args.chain_id:
303+
print(f"Updating chain id to {args.chain_id}...")
304+
genesis["chain_id"] = args.chain_id
305+
306+
print("Writing new genesis file...")
307+
with open(f"{args.home_path}/config/genesis.json", "w") as f:
308+
json.dump(genesis, f, indent=2)
309+
310+
print(f"Done! Wrote new genesis at {args.home_path}/config/genesis.json")
311+
print("You can now start the chain:")
312+
print()
313+
print(
314+
f"fetchd --home {args.home_path} tendermint unsafe-reset-all && fetchd --home {args.home_path} start"
315+
)
316+
print()
317+
318+
319+
if __name__ == "__main__":
320+
main()

0 commit comments

Comments
 (0)