Created
February 26, 2025 09:24
-
-
Save skhomuti/bc188884f6bdc1ce2c65a949e84b0dc5 to your computer and use it in GitHub Desktop.
Check fee recipient on the relays for CSM Node Operators
This file contains hidden or 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
# pre-requisite: python3, web3, requests | |
# usage: python3 check_no_relays.py | |
import os | |
from json import JSONDecodeError | |
import requests | |
from requests.adapters import HTTPAdapter, Retry | |
from web3 import Web3 | |
# Set up web3 connection | |
RPC_URL = os.getenv("RPC_URL") | |
web3 = Web3(Web3.HTTPProvider(RPC_URL)) | |
# Contract details | |
CSM_ADDRESS = "0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F" | |
RELAYS_ALLOWED_LIST = "0xF95f069F9AD107938F6ba802a3da87892298610E" | |
EXPECTED_FEE_RECIPIENT = "0x388c818ca8b9251b393131c08a736a67ccb19297" | |
CSM_ABI = ''' | |
[ | |
{ | |
"constant": true, | |
"inputs": [{"name": "_id", "type": "uint256"}], | |
"name": "getNodeOperator", | |
"outputs": [{ | |
"components": [ | |
{ | |
"internalType": "uint32", | |
"name": "totalAddedKeys", | |
"type": "uint32" | |
}, | |
{ | |
"internalType": "uint32", | |
"name": "totalWithdrawnKeys", | |
"type": "uint32" | |
}, | |
{ | |
"internalType": "uint32", | |
"name": "totalDepositedKeys", | |
"type": "uint32" | |
}, | |
{ | |
"internalType": "uint32", | |
"name": "totalVettedKeys", | |
"type": "uint32" | |
}, | |
{ | |
"internalType": "uint32", | |
"name": "stuckValidatorsCount", | |
"type": "uint32" | |
}, | |
{ | |
"internalType": "uint32", | |
"name": "depositableValidatorsCount", | |
"type": "uint32" | |
}, | |
{ | |
"internalType": "uint32", | |
"name": "targetLimit", | |
"type": "uint32" | |
}, | |
{ | |
"internalType": "uint8", | |
"name": "targetLimitMode", | |
"type": "uint8" | |
}, | |
{ | |
"internalType": "uint32", | |
"name": "totalExitedKeys", | |
"type": "uint32" | |
}, | |
{ | |
"internalType": "uint32", | |
"name": "enqueuedCount", | |
"type": "uint32" | |
}, | |
{ | |
"internalType": "address", | |
"name": "managerAddress", | |
"type": "address" | |
}, | |
{ | |
"internalType": "address", | |
"name": "proposedManagerAddress", | |
"type": "address" | |
}, | |
{ | |
"internalType": "address", | |
"name": "rewardAddress", | |
"type": "address" | |
}, | |
{ | |
"internalType": "address", | |
"name": "proposedRewardAddress", | |
"type": "address" | |
}, | |
{ | |
"internalType": "bool", | |
"name": "extendedManagerPermissions", | |
"type": "bool" | |
}], | |
"internalType": "struct NodeOperator", | |
"name": "", | |
"type": "tuple" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"constant": true, | |
"inputs": [{"name": "_id", "type": "uint256"}, {"name": "start", "type": "uint256"}, {"name": "count", "type": "uint256"}], | |
"name": "getSigningKeys", | |
"outputs": [{"name": "", "type": "bytes"}], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
} | |
] | |
''' | |
RELAYS_ABI = ''' | |
[ | |
{ | |
"constant": true, | |
"inputs": [], | |
"name": "get_relays", | |
"outputs": [ | |
{ | |
"components": [ | |
{"name": "uri", "type": "string"}, | |
{"name": "operator", "type": "string"}, | |
{"name": "is_mandatory", "type": "bool"}, | |
{"name": "description", "type": "string"} | |
], | |
"name": "", | |
"type": "tuple[]" | |
} | |
], | |
"payable": false, | |
"stateMutability": "view", | |
"type": "function" | |
} | |
] | |
''' | |
# Instantiate contract | |
csm = web3.eth.contract(address=CSM_ADDRESS, abi=CSM_ABI) | |
relays_allowed_list = web3.eth.contract(address=RELAYS_ALLOWED_LIST, abi=RELAYS_ABI) | |
def fetch_signing_keys(node_operator_id): | |
print("Fetching singing keys...") | |
# Get total deposited keys | |
node_operator = csm.functions.getNodeOperator(node_operator_id).call() | |
total_keys = node_operator[0] # Assuming it's the first return value | |
if total_keys == 0: | |
print(f"No keys found for node operator {node_operator_id}") | |
return [] | |
# Fetch signing keys in batches of 50 | |
batch_size = 50 | |
signing_keys = [] | |
for start in range(0, total_keys, batch_size): | |
batch_keys = csm.functions.getSigningKeys(node_operator_id, start, min(batch_size, total_keys - start)).call() | |
decoded_keys = ["0x" + batch_keys[i:i + 48].hex() for i in range(0, len(batch_keys), 48)] # Convert to hex string | |
signing_keys.extend(decoded_keys) | |
return signing_keys | |
def fetch_relays(): | |
print("Fetching relays...") | |
return [relay[0] for relay in relays_allowed_list.functions.get_relays().call()] | |
def fetch_fee_recipient(session, relay, key): | |
url = relay + "/relay/v1/data/validator_registration?pubkey=" + key | |
response = session.get(url) | |
try: | |
data = response.json() | |
except JSONDecodeError: | |
print(f"Failed to decode data from {url}", response.text) | |
return None | |
message = data["message"] | |
if isinstance(message, str) and message.startswith("no registration found for validator"): | |
return None | |
return message.get("fee_recipient") | |
def check_fee_recipient(node_operator_id): | |
relays = fetch_relays() | |
keys = fetch_signing_keys(node_operator_id) | |
# Set up session with retries | |
session = requests.Session() | |
retries = Retry(total=5, backoff_factor=1, raise_on_status=False) | |
adapter = HTTPAdapter(max_retries=retries) | |
session.mount("http://", adapter) | |
session.mount("https://", adapter) | |
for relay in relays: | |
print(f"Checking relay {relay}...\n") | |
for key in keys: | |
fee_recipient = fetch_fee_recipient(session, relay, key) | |
if not fee_recipient: | |
print("No registration found for key " + key) | |
continue | |
if fee_recipient.lower() != EXPECTED_FEE_RECIPIENT: | |
print(f"[ERROR] Fee recipient for key {key} is incorrect\n", | |
f"Check it here: " + relay + "/relay/v1/data/validator_registration?pubkey=" + key) | |
else: | |
print("[OK] Fee recipient for key " + key + " is correct.") | |
if __name__ == "__main__": | |
node_operator_id = int(input("Enter node operator ID: ")) | |
check_fee_recipient(node_operator_id) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment