11#!/usr/bin/env python3
22"""
33Create split account file from old genesis addresses.
4- Usage: python merge_genesis.py <addresses.json> <main_genesis_file.json> [--balance-multiplier N] [--network NAME ]
4+ Usage: python merge_genesis.py <addresses.json> <main_genesis_file.json> [OPTIONS ]
55
66This script creates a new split account file that will be automatically
77merged when the network starts, instead of modifying the main genesis file.
88
99Options:
1010 --balance-multiplier N Multiply all balances by N (default: 1)
1111 --network NAME Network name for split files (overrides auto-detection)
12+ --add-balance Add balance to existing addresses in split files instead of skipping duplicates
1213"""
1314
1415import json
@@ -176,9 +177,14 @@ def find_next_split_number(genesis_dir: str, base_name: str) -> int:
176177
177178 return max (numbers ) + 1 if numbers else 1
178179
179- def get_existing_addresses_from_all_sources (genesis_dir : str , base_name : str , main_genesis : Dict ) -> tuple [Set [str ], Set [str ]]:
180- """Get existing addresses from main genesis and all split files"""
180+ def get_existing_addresses_from_all_sources (genesis_dir : str , base_name : str , main_genesis : Dict ) -> tuple [Set [str ], Set [str ], Dict [str , str ]]:
181+ """Get existing addresses from main genesis and all split files
182+
183+ Returns:
184+ tuple: (existing_eth_addresses, existing_cosmos_addresses, cosmos_to_split_file_map)
185+ """
181186 existing_eth_addresses , existing_cosmos_addresses = get_existing_addresses (main_genesis )
187+ cosmos_to_split_file_map = {} # Maps cosmos address -> split file path
182188
183189 # Also check split files for existing addresses
184190 pattern = os .path .join (genesis_dir , f"{ base_name } .genesis.accounts.*.json" )
@@ -194,18 +200,65 @@ def get_existing_addresses_from_all_sources(genesis_dir: str, base_name: str, ma
194200 cosmos_addr = account .get ('address' , '' )
195201 if cosmos_addr and cosmos_addr .startswith ('shardeum' ):
196202 existing_cosmos_addresses .add (cosmos_addr )
203+ cosmos_to_split_file_map [cosmos_addr ] = split_file
197204
198205 # Check balances for cosmos addresses
199206 for balance in split_data .get ('balances' , []):
200207 cosmos_addr = balance .get ('address' , '' )
201208 if cosmos_addr and cosmos_addr .startswith ('shardeum' ):
202209 existing_cosmos_addresses .add (cosmos_addr )
210+ cosmos_to_split_file_map [cosmos_addr ] = split_file
203211
204212 except Exception as e :
205213 print (f"Warning: Could not read split file { split_file } : { e } " , file = sys .stderr )
206214
207215 print (f"Found { len (existing_eth_addresses )} existing Ethereum addresses and { len (existing_cosmos_addresses )} existing Cosmos addresses" )
208- return existing_eth_addresses , existing_cosmos_addresses
216+ return existing_eth_addresses , existing_cosmos_addresses , cosmos_to_split_file_map
217+
218+ def update_balance_in_split_file (split_file_path : str , cosmos_address : str , balance_to_add : int ) -> bool :
219+ """Update balance in an existing split file by adding to existing balance
220+
221+ Args:
222+ split_file_path: Path to the split file
223+ cosmos_address: Cosmos address to update
224+ balance_to_add: Amount to add to existing balance
225+
226+ Returns:
227+ bool: True if successful, False otherwise
228+ """
229+ try :
230+ # Load the split file
231+ with open (split_file_path , 'r' ) as f :
232+ split_data = json .load (f )
233+
234+ # Find and update the balance
235+ balance_updated = False
236+ for balance_entry in split_data .get ('balances' , []):
237+ if balance_entry .get ('address' ) == cosmos_address :
238+ for coin in balance_entry .get ('coins' , []):
239+ if coin .get ('denom' ) == 'ashm' :
240+ old_amount = int (coin .get ('amount' , '0' ))
241+ new_amount = old_amount + balance_to_add
242+ coin ['amount' ] = str (new_amount )
243+ print (f"Updated balance in { split_file_path } : { cosmos_address } from { old_amount :,} to { new_amount :,} ashm (+{ balance_to_add :,} )" )
244+ balance_updated = True
245+ break
246+ if balance_updated :
247+ break
248+
249+ if not balance_updated :
250+ print (f"Warning: Could not find balance entry for { cosmos_address } in { split_file_path } " , file = sys .stderr )
251+ return False
252+
253+ # Save the updated split file
254+ with open (split_file_path , 'w' ) as f :
255+ json .dump (split_data , f , indent = 2 )
256+
257+ return True
258+
259+ except Exception as e :
260+ print (f"Error updating balance in split file { split_file_path } : { e } " , file = sys .stderr )
261+ return False
209262
210263def update_main_genesis_supply (main_genesis_path : str , added_balance : int ) -> bool :
211264 """Update the total supply in the main genesis file"""
@@ -246,7 +299,7 @@ def update_main_genesis_supply(main_genesis_path: str, added_balance: int) -> bo
246299 print (f"Error updating main genesis supply: { e } " , file = sys .stderr )
247300 return False
248301
249- def merge_genesis_files (old_genesis_path : str , new_genesis_path : str , balance_multiplier : int = 1 , network : str = None ):
302+ def merge_genesis_files (old_genesis_path : str , new_genesis_path : str , balance_multiplier : int = 1 , network : str = None , add_balance : bool = False ):
250303 """Create split account file from old genesis addresses"""
251304 print (f"Loading old genesis from { old_genesis_path } " )
252305 old_genesis = load_old_genesis (old_genesis_path )
@@ -255,6 +308,10 @@ def merge_genesis_files(old_genesis_path: str, new_genesis_path: str, balance_mu
255308 main_genesis = load_new_genesis (new_genesis_path )
256309
257310 print (f"Using balance multiplier: { balance_multiplier } " )
311+ if add_balance :
312+ print (f"Add balance mode: ENABLED (will add to existing balances in split files)" )
313+ else :
314+ print (f"Add balance mode: DISABLED (will skip duplicate addresses)" )
258315
259316 # Determine base name and directory for split files
260317 genesis_path = Path (new_genesis_path )
@@ -270,40 +327,30 @@ def merge_genesis_files(old_genesis_path: str, new_genesis_path: str, balance_mu
270327 print (f"Auto-detected base name: { base_name } " )
271328
272329 # Get existing addresses from ALL sources (main genesis + split files)
273- existing_eth_addresses , existing_cosmos_addresses = get_existing_addresses_from_all_sources (genesis_dir , base_name , main_genesis )
330+ existing_eth_addresses , existing_cosmos_addresses , cosmos_to_split_file_map = get_existing_addresses_from_all_sources (genesis_dir , base_name , main_genesis )
274331
275332 # Prepare data for new split file
276333 new_accounts = []
277334 new_balances = []
278335 added_count = 0
279336 skipped_count = 0
337+ updated_count = 0
280338 total_added_balance = 0
281-
339+
282340 print (f"Processing { len (old_genesis )} addresses from old genesis" )
283-
341+
284342 for eth_address , balance_data in old_genesis .items ():
285343 # Normalize ethereum address
286344 eth_addr_normalized = eth_address .lower ()
287-
345+
288346 # Convert to cosmos address first to check for duplicates
289347 cosmos_address = eth_to_cosmos_address (eth_address )
290348 if not cosmos_address :
291349 print (f"Failed to convert address: { eth_address } " )
292350 skipped_count += 1
293351 continue
294-
295- # Check if either ethereum or cosmos address already exists
296- if eth_addr_normalized in existing_eth_addresses :
297- print (f"Skipping duplicate Ethereum address: { eth_address } " )
298- skipped_count += 1
299- continue
300-
301- if cosmos_address in existing_cosmos_addresses :
302- print (f"Skipping duplicate Cosmos address: { cosmos_address } (from Ethereum { eth_address } )" )
303- skipped_count += 1
304- continue
305-
306- # Get balance in wei
352+
353+ # Get balance in wei early (needed for both adding and updating)
307354 balance_wei = balance_data .get ('wei' , '0' )
308355 balance_int = int (balance_wei )
309356
@@ -317,6 +364,33 @@ def merge_genesis_files(old_genesis_path: str, new_genesis_path: str, balance_mu
317364 skipped_count += 1
318365 continue
319366
367+ # Check if either ethereum or cosmos address already exists
368+ if eth_addr_normalized in existing_eth_addresses :
369+ print (f"Skipping duplicate Ethereum address in main genesis: { eth_address } " )
370+ skipped_count += 1
371+ continue
372+
373+ if cosmos_address in existing_cosmos_addresses :
374+ # Address exists - check if we should add to existing balance or skip
375+ if add_balance and cosmos_address in cosmos_to_split_file_map :
376+ # Add balance mode: update the existing balance in the split file
377+ split_file = cosmos_to_split_file_map [cosmos_address ]
378+ if update_balance_in_split_file (split_file , cosmos_address , balance_int ):
379+ total_added_balance += balance_int
380+ updated_count += 1
381+ else :
382+ print (f"Failed to update balance for { cosmos_address } (from Ethereum { eth_address } )" )
383+ skipped_count += 1
384+ continue
385+ else :
386+ # Normal mode: skip duplicates
387+ if cosmos_address in cosmos_to_split_file_map :
388+ print (f"Skipping duplicate Cosmos address in split file: { cosmos_address } (from Ethereum { eth_address } )" )
389+ else :
390+ print (f"Skipping duplicate Cosmos address in main genesis: { cosmos_address } (from Ethereum { eth_address } )" )
391+ skipped_count += 1
392+ continue
393+
320394 # Create auth account and bank balance for the split file
321395 auth_account = create_auth_account (cosmos_address )
322396 bank_balance = create_bank_balance (cosmos_address , balance_wei )
@@ -337,50 +411,55 @@ def merge_genesis_files(old_genesis_path: str, new_genesis_path: str, balance_mu
337411 else :
338412 print (f"Will add address: { eth_address } -> { cosmos_address } (balance: { balance_wei } wei)" )
339413
340- if added_count == 0 :
341- print ("No new addresses to add. No split file created." )
342- return
343-
344- # Sort accounts and balances for consistency
345- new_accounts .sort (key = lambda x : x ['address' ])
346- new_balances .sort (key = lambda x : x ['address' ])
347-
348- # Create split file data
349- split_data = {
350- 'accounts' : new_accounts ,
351- 'balances' : new_balances
352- }
353-
354- # Find next split file number and create the file
355- next_number = find_next_split_number (genesis_dir , base_name )
356- split_file_path = os .path .join (genesis_dir , f"{ base_name } .genesis.accounts.{ next_number } .json" )
357-
358- print (f"\n Creating split file:" )
359- print (f" Added: { added_count } addresses" )
414+ # Print summary
415+ print (f"\n Processing summary:" )
416+ print (f" New addresses added: { added_count } " )
417+ print (f" Existing addresses updated: { updated_count } " )
360418 print (f" Skipped: { skipped_count } addresses (duplicates or conversion errors)" )
361419 if balance_multiplier != 1 :
362420 print (f" Balance multiplier: { balance_multiplier } " )
363- print (f" Total added balance : { total_added_balance :,} ashm" )
421+ print (f" Total balance added to genesis : { total_added_balance :,} ashm" )
364422
365- # Save split file
366- try :
367- with open (split_file_path , 'w' ) as f :
368- json .dump (split_data , f , indent = 2 )
369- print (f"Successfully created split file: { split_file_path } " )
370- except Exception as e :
371- print (f"Error creating split file: { e } " , file = sys .stderr )
372- sys .exit (1 )
423+ # Create new split file only if we have new addresses
424+ if added_count > 0 :
425+ # Sort accounts and balances for consistency
426+ new_accounts .sort (key = lambda x : x ['address' ])
427+ new_balances .sort (key = lambda x : x ['address' ])
428+
429+ # Create split file data
430+ split_data = {
431+ 'accounts' : new_accounts ,
432+ 'balances' : new_balances
433+ }
434+
435+ # Find next split file number and create the file
436+ next_number = find_next_split_number (genesis_dir , base_name )
437+ split_file_path = os .path .join (genesis_dir , f"{ base_name } .genesis.accounts.{ next_number } .json" )
438+
439+ # Save split file
440+ try :
441+ with open (split_file_path , 'w' ) as f :
442+ json .dump (split_data , f , indent = 2 )
443+ print (f"\n Successfully created new split file: { split_file_path } " )
444+ except Exception as e :
445+ print (f"Error creating split file: { e } " , file = sys .stderr )
446+ sys .exit (1 )
447+ else :
448+ print (f"\n No new addresses to add. No new split file created." )
373449
374- # Update main genesis supply
450+ # Update main genesis supply if we added any balance (from new or updated addresses)
375451 if total_added_balance > 0 :
376- print (f"Updating main genesis supply..." )
452+ print (f"\n Updating main genesis supply..." )
377453 if not update_main_genesis_supply (new_genesis_path , total_added_balance ):
378454 print (f"Warning: Failed to update supply in main genesis" , file = sys .stderr )
379455 else :
380456 print (f"Successfully updated supply in main genesis" )
381457
382- print (f"\n Split file creation complete!" )
383- print (f"The new accounts will be automatically included when the network starts." )
458+ print (f"\n Operation complete!" )
459+ if added_count > 0 :
460+ print (f"The new accounts will be automatically included when the network starts." )
461+ if updated_count > 0 :
462+ print (f"Updated balances have been added to existing accounts in split files." )
384463
385464def main ():
386465 parser = argparse .ArgumentParser (description = "Create split account file from old genesis addresses" )
@@ -390,10 +469,12 @@ def main():
390469 help = "Multiplier to apply to all balances (default: 1)" )
391470 parser .add_argument ("--network" , type = str , default = None ,
392471 help = "Network name to use for split files (overrides auto-detection)" )
472+ parser .add_argument ("--add-balance" , action = "store_true" ,
473+ help = "Add balance to existing addresses in split files instead of skipping duplicates" )
393474
394475 args = parser .parse_args ()
395476
396- merge_genesis_files (args .old_genesis , args .main_genesis , args .balance_multiplier , args .network )
477+ merge_genesis_files (args .old_genesis , args .main_genesis , args .balance_multiplier , args .network , args . add_balance )
397478
398479if __name__ == "__main__" :
399480 main ()
0 commit comments