mirror of
https://github.com/logos-messaging/logos-messaging-simulator.git
synced 2026-01-02 14:03:07 +00:00
Add token-mint-service as tool
This commit is contained in:
parent
c6b201d264
commit
363d373cc0
@ -2,9 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
# 1. Install foundry, pnpm, and required tools
|
||||
apt update && apt install -y jq
|
||||
|
||||
# 1. Install foundry and pnpm
|
||||
curl -L https://foundry.paradigm.xyz | bash && . /root/.bashrc && foundryup && export PATH=$PATH:$HOME/.foundry/bin
|
||||
|
||||
echo "installing pnpm..."
|
||||
|
||||
20
tools/token-mint-service/Dockerfile
Normal file
20
tools/token-mint-service/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
||||
FROM python:3.11-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system packages needed by the script
|
||||
RUN apk add --no-cache bind-tools jq
|
||||
|
||||
# Install requirements
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy scripts
|
||||
COPY init_node_tokens.py .
|
||||
COPY get_account_key.sh .
|
||||
|
||||
# Make scripts executable
|
||||
RUN chmod +x /app/init_node_tokens.py /app/get_account_key.sh
|
||||
|
||||
# Use the account key helper as entrypoint
|
||||
ENTRYPOINT ["/bin/sh", "/app/get_account_key.sh"]
|
||||
57
tools/token-mint-service/get_account_key.sh
Normal file
57
tools/token-mint-service/get_account_key.sh
Normal file
@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
# Helper script to get the index of the container and use it to retrieve a unique account private key per nwaku node to be used to generate the keystore
|
||||
|
||||
set -e
|
||||
|
||||
# Tools already installed in Dockerfile
|
||||
|
||||
ANVIL_CONFIG_PATH=${ANVIL_CONFIG_PATH:-/shared/anvil-config.txt}
|
||||
|
||||
# Wait for anvil config to be available
|
||||
echo "Waiting for anvil config at $ANVIL_CONFIG_PATH..."
|
||||
while [ ! -f "$ANVIL_CONFIG_PATH" ]; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Get container IP and determine index (same method as run_nwaku.sh)
|
||||
IP=$(ip a | grep "inet " | grep -Fv 127.0.0.1 | sed 's/.*inet \([^/]*\).*/\1/')
|
||||
echo "Container IP: $IP"
|
||||
|
||||
# Extract container name from reverse DNS lookup and get index
|
||||
CNTR=$(dig -x $IP +short | cut -d'.' -f1)
|
||||
INDEX=$(echo $CNTR | sed 's/.*[-_]\([0-9]*\)/\1/')
|
||||
|
||||
if [ $? -ne 0 ] || [ -z "$INDEX" ]; then
|
||||
echo "Error: Failed to determine the replica index from IP." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Determined container index: $INDEX"
|
||||
|
||||
# Read anvil config
|
||||
json_content=$(cat "$ANVIL_CONFIG_PATH")
|
||||
if [ -z "$json_content" ]; then
|
||||
echo "Error: Failed to read the JSON file or the file is empty." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get private key and address for this index
|
||||
ARRAY_INDEX=$((INDEX - 1))
|
||||
|
||||
ACCOUNT_PRIVATE_KEY=$(echo "$json_content" | jq -r ".private_keys[$ARRAY_INDEX]")
|
||||
ACCOUNT_ADDRESS=$(echo "$json_content" | jq -r ".available_accounts[$ARRAY_INDEX]")
|
||||
|
||||
if [ "$ACCOUNT_PRIVATE_KEY" = "null" ] || [ "$ACCOUNT_ADDRESS" = "null" ]; then
|
||||
echo "Failed to get account private key or address for index $INDEX (array index $ARRAY_INDEX)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Export for the Python script
|
||||
export NODE_PRIVATE_KEY="$ACCOUNT_PRIVATE_KEY"
|
||||
export NODE_ADDRESS="$ACCOUNT_ADDRESS"
|
||||
export NODE_INDEX="$INDEX"
|
||||
|
||||
echo "Node $INDEX using Ethereum account: $ACCOUNT_ADDRESS"
|
||||
|
||||
# Run the Python initialization script
|
||||
exec python3 /app/init_node_tokens.py
|
||||
194
tools/token-mint-service/init_node_tokens.py
Normal file
194
tools/token-mint-service/init_node_tokens.py
Normal file
@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Per-node token initialization service.
|
||||
|
||||
This script runs as an init container for each nwaku node to:
|
||||
1. Mint ERC20 tokens to the node's address
|
||||
2. Approve the RLN contract to spend those tokens
|
||||
|
||||
Each node gets its own private key and handles its own token setup.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
from web3 import Web3
|
||||
from web3.exceptions import TransactionNotFound
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - [Node Init] %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class NodeTokenInitializer:
|
||||
def __init__(self):
|
||||
"""Initialize the node token service."""
|
||||
# Required environment variables
|
||||
self.rpc_url = os.getenv('RPC_URL', 'http://foundry:8545')
|
||||
self.token_address = os.getenv('TOKEN_ADDRESS', '0x5FbDB2315678afecb367f032d93F642f64180aa3')
|
||||
self.contract_address = os.getenv('CONTRACT_ADDRESS', '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707')
|
||||
self.private_key = os.getenv('NODE_PRIVATE_KEY') # Ethereum account private key (not nwaku node-key)
|
||||
self.node_address = os.getenv('NODE_ADDRESS') # Ethereum account address
|
||||
self.node_index = os.getenv('NODE_INDEX', '0')
|
||||
self.mint_amount = int(os.getenv('MINT_AMOUNT', '5000000000000000000')) # 5 tokens
|
||||
|
||||
if not self.private_key:
|
||||
raise ValueError("NODE_PRIVATE_KEY (Ethereum account private key) environment variable is required")
|
||||
if not self.node_address:
|
||||
raise ValueError("NODE_ADDRESS (Ethereum account address) environment variable is required")
|
||||
|
||||
# Initialize Web3
|
||||
self.w3 = Web3(Web3.HTTPProvider(self.rpc_url))
|
||||
if not self.w3.is_connected():
|
||||
raise Exception(f"Failed to connect to Ethereum node at {self.rpc_url}")
|
||||
|
||||
# Convert addresses to proper checksum format
|
||||
self.node_address = self.w3.to_checksum_address(self.node_address)
|
||||
self.token_address = self.w3.to_checksum_address(self.token_address)
|
||||
self.contract_address = self.w3.to_checksum_address(self.contract_address)
|
||||
|
||||
logger.info(f"Node {self.node_index} initializing tokens")
|
||||
logger.info(f"Address: {self.node_address}")
|
||||
logger.info(f"Token: {self.token_address}")
|
||||
logger.info(f"Contract: {self.contract_address}")
|
||||
|
||||
def wait_for_transaction(self, tx_hash: str, timeout: int = 120) -> bool:
|
||||
"""Wait for transaction to be mined."""
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
receipt = self.w3.eth.get_transaction_receipt(tx_hash)
|
||||
if receipt.status == 1:
|
||||
logger.info(f"Transaction {tx_hash} confirmed")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Transaction {tx_hash} failed with status {receipt.status}")
|
||||
return False
|
||||
except TransactionNotFound:
|
||||
time.sleep(2)
|
||||
continue
|
||||
|
||||
logger.error(f"Transaction {tx_hash} timed out after {timeout} seconds")
|
||||
return False
|
||||
|
||||
def mint_tokens(self) -> bool:
|
||||
"""Mint tokens to this node's address using the node's own private key."""
|
||||
try:
|
||||
logger.info(f"Minting {self.mint_amount} tokens to {self.node_address}")
|
||||
|
||||
# Use the node's own private key since mint() is public
|
||||
nonce = self.w3.eth.get_transaction_count(self.node_address)
|
||||
|
||||
# Build mint transaction
|
||||
function_signature = self.w3.keccak(text="mint(address,uint256)")[:4]
|
||||
encoded_address = self.node_address[2:].lower().zfill(64)
|
||||
encoded_amount = hex(self.mint_amount)[2:].zfill(64)
|
||||
data = function_signature.hex() + encoded_address + encoded_amount
|
||||
|
||||
transaction = {
|
||||
'to': self.token_address,
|
||||
'value': 0,
|
||||
'gas': 200000,
|
||||
'gasPrice': self.w3.eth.gas_price,
|
||||
'nonce': nonce,
|
||||
'data': data,
|
||||
}
|
||||
|
||||
# Sign and send with node's own key
|
||||
signed_txn = self.w3.eth.account.sign_transaction(transaction, self.private_key)
|
||||
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
|
||||
|
||||
logger.info(f"Mint transaction sent: {tx_hash.hex()}")
|
||||
|
||||
if self.wait_for_transaction(tx_hash.hex()):
|
||||
logger.info(f"✓ Mint successful for node {self.node_index}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"✗ Mint failed for node {self.node_index}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"✗ Mint failed for node {self.node_index}: {str(e)}")
|
||||
return False
|
||||
|
||||
def approve_tokens(self) -> bool:
|
||||
"""Approve RLN contract to spend tokens."""
|
||||
try:
|
||||
logger.info(f"Approving {self.mint_amount} tokens for contract {self.contract_address}")
|
||||
|
||||
nonce = self.w3.eth.get_transaction_count(self.node_address)
|
||||
|
||||
# Build approve transaction
|
||||
function_signature = self.w3.keccak(text="approve(address,uint256)")[:4]
|
||||
encoded_contract = self.contract_address[2:].lower().zfill(64)
|
||||
encoded_amount = hex(self.mint_amount)[2:].zfill(64)
|
||||
data = function_signature.hex() + encoded_contract + encoded_amount
|
||||
|
||||
transaction = {
|
||||
'to': self.token_address,
|
||||
'value': 0,
|
||||
'gas': 200000,
|
||||
'gasPrice': self.w3.eth.gas_price,
|
||||
'nonce': nonce,
|
||||
'data': data,
|
||||
}
|
||||
|
||||
# Sign and send with node's own key
|
||||
signed_txn = self.w3.eth.account.sign_transaction(transaction, self.private_key)
|
||||
tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction)
|
||||
|
||||
logger.info(f"Approve transaction sent: {tx_hash.hex()}")
|
||||
|
||||
if self.wait_for_transaction(tx_hash.hex()):
|
||||
logger.info(f"✓ Approval successful for node {self.node_index}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"✗ Approval failed for node {self.node_index}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"✗ Approval failed for node {self.node_index}: {str(e)}")
|
||||
return False
|
||||
|
||||
def run(self) -> bool:
|
||||
"""Run the token initialization process."""
|
||||
try:
|
||||
logger.info(f"Starting token initialization for node {self.node_index}")
|
||||
|
||||
# Step 1: Mint tokens
|
||||
if not self.mint_tokens():
|
||||
return False
|
||||
|
||||
# Step 2: Approve contract
|
||||
if not self.approve_tokens():
|
||||
return False
|
||||
|
||||
logger.info(f"✓ Node {self.node_index} token initialization completed successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"✗ Node {self.node_index} initialization failed: {str(e)}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
try:
|
||||
initializer = NodeTokenInitializer()
|
||||
success = initializer.run()
|
||||
|
||||
if success:
|
||||
logger.info("Node ready to start")
|
||||
sys.exit(0)
|
||||
else:
|
||||
logger.error("Node initialization failed")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
tools/token-mint-service/requirements.txt
Normal file
1
tools/token-mint-service/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
web3==6.15.1
|
||||
Loading…
x
Reference in New Issue
Block a user