-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic reconciliation debugging script
- Loading branch information
Eela Nagaraj
committed
Sep 22, 2023
1 parent
8d60be8
commit 8b5a3bf
Showing
1 changed file
with
188 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
# Simple scripts for investigating reconciliation failures. | ||
# Usage help: python3 scripts/reconciliation_debugging.py --help | ||
|
||
import requests | ||
import argparse | ||
|
||
|
||
def wrap_response(response): | ||
if response.status_code != 200: | ||
raise ValueError("Request failed: ", response.text) | ||
return response.json() | ||
|
||
|
||
def network_req_from_chain_id(chain_id): | ||
return {"blockchain": "celo", "network": f"{chain_id}"} | ||
|
||
|
||
def request_account_balance(node_url, network_id, address, block_number): | ||
return wrap_response( | ||
requests.post( | ||
f"{node_url}/account/balance", | ||
json={ | ||
"network_identifier": network_id, | ||
"account_identifier": { | ||
"address": address, | ||
}, | ||
"block_identifier": {"index": block_number}, | ||
}, | ||
) | ||
) | ||
|
||
|
||
def request_block(node_url, network_id, block_number): | ||
""" | ||
Query blockscout API for block_tx | ||
""" | ||
req_json = { | ||
"network_identifier": network_id, | ||
"block_identifier": {"index": block_number}, | ||
} | ||
return wrap_response(requests.post(f"{node_url}/block", json=req_json)) | ||
|
||
|
||
def request_block_tx(node_url, network_id, address, block_number, block_hash, tx_hash): | ||
return wrap_response( | ||
requests.post( | ||
f"{node_url}/block/transaction", | ||
json={ | ||
"network_identifier": network_id, | ||
"account_identifier": { | ||
"address": address, | ||
}, | ||
"block_identifier": {"index": block_number, "hash": block_hash}, | ||
"transaction_identifier": {"hash": tx_hash}, | ||
}, | ||
) | ||
) | ||
|
||
|
||
def account_balance( | ||
block_number, | ||
node_url, | ||
reconcile_address, | ||
network_id, | ||
): | ||
# get account/balance balance | ||
resp = request_account_balance( | ||
node_url, network_id, reconcile_address, block_number | ||
) | ||
|
||
balances = resp["balances"] | ||
|
||
# if not, let's investigate this | ||
assert len(balances) == 1 | ||
return int(balances[0]["value"]) | ||
|
||
|
||
def block_tx_balance_change( | ||
block_number, | ||
block_hash, | ||
tx_hash, | ||
node_url, | ||
reconcile_address, | ||
network_id, | ||
): | ||
resp = request_block_tx( | ||
node_url, | ||
network_id, | ||
reconcile_address, | ||
block_number, | ||
block_hash, | ||
tx_hash, | ||
) | ||
try: | ||
ops = resp["transaction"].get("operations", []) # or [] | ||
return sum( | ||
[ | ||
int(op["amount"]["value"]) | ||
for op in ops | ||
if op["account"]["address"] == reconcile_address | ||
and op["status"] == "success" | ||
] | ||
) | ||
|
||
except Exception as e: | ||
print(f"Exception for response: {resp}") | ||
raise e | ||
|
||
|
||
# Useful when trying to shrink the range to search over and reconcile, | ||
# i.e. when it's not yet known when exactly the reconciliation error occurs. | ||
def scan_account_balance_changes( | ||
start_block, | ||
end_block, | ||
step, | ||
node_url, | ||
reconcile_address, | ||
network_id, | ||
): | ||
balance = None | ||
balance_changes = [] | ||
for block_number in range(start_block, end_block, step): | ||
curr_balance = account_balance( | ||
block_number, node_url, reconcile_address, network_id | ||
) | ||
if balance != curr_balance: | ||
print(f"balance change at: {block_number}, curr_balance: {curr_balance}") | ||
balance_changes.append(balance) | ||
balance = curr_balance | ||
return balance_changes | ||
|
||
|
||
# Performs targetted balance reconciliation (same idea as Rosetta CLI checks) | ||
# over a restricted range and for a particular address. | ||
def reconcile_range( | ||
start_block, | ||
end_block, | ||
node_url, | ||
reconcile_address, | ||
network_id, | ||
): | ||
assert start_block > 0 | ||
curr_balance = account_balance( | ||
start_block - 1, node_url, reconcile_address, network_id | ||
) | ||
print(f"starting balance: {curr_balance}") | ||
|
||
for i in range(start_block, end_block): | ||
print(f"beginning block {i}") | ||
# fetch account_balance changes | ||
new_balance = account_balance(i, node_url, reconcile_address, network_id) | ||
block = request_block(node_url, network_id, i) | ||
block_hash = block["block"]["block_identifier"]["hash"] | ||
|
||
sum_ops_change = sum( | ||
[ | ||
block_tx_balance_change( | ||
i, | ||
block_hash, | ||
tx["hash"], | ||
node_url, | ||
reconcile_address, | ||
network_id, | ||
) | ||
for tx in block.get("other_transactions", []) | ||
] | ||
) | ||
assert ( | ||
curr_balance + sum_ops_change == new_balance | ||
), f"failure for block {i}, curr_balance: {curr_balance}, new_balance: {new_balance}, sum_ops_change: {sum_ops_change}" | ||
curr_balance = new_balance | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument("start", type=int, help="starting block in range") | ||
parser.add_argument("end", type=int, help="ending block in range") | ||
parser.add_argument("chain_id", type=int, help="chain ID in genesis.json") | ||
parser.add_argument( | ||
"address", type=str, help="address whose balance to reconcile over range" | ||
) | ||
parser.add_argument( | ||
"-n", "--node_url", default="http://localhost:8080", help="rosetta RPC URL" | ||
) | ||
args = parser.parse_args() | ||
network_req = network_req_from_chain_id(args.chain_id) | ||
|
||
reconcile_range(args.start, args.end, args.node_url, args.address, network_req) |