mirror of
https://github.com/logos-messaging/logos-messaging-simulator.git
synced 2026-01-02 14:03:07 +00:00
Update contract deployment for new RLN contract (#110)
* Run deploy scripts for new incentivized contract and mint tokens per account * Add service to manage RLN contract token minting per nwaku service * Add token-mint-service as tool * revert back to sh instead of bash for contract deployer script * Add comments and code cleanup
This commit is contained in:
parent
5f0dfb50ea
commit
e5bb677abe
@ -21,10 +21,7 @@ fi
|
||||
cd /waku-rlnv2-contract
|
||||
git checkout $RLN_CONTRACT_REPO_COMMIT
|
||||
|
||||
#3. Replace the hardcoded MAX_MESSAGE_LIMIT
|
||||
sed -i "s/\b100\b/${MAX_MESSAGE_LIMIT}/g" script/Deploy.s.sol
|
||||
|
||||
# 4. Compile
|
||||
# 3. Compile Contract Repo
|
||||
echo "forge install..."
|
||||
forge install
|
||||
echo "pnpm install..."
|
||||
@ -32,7 +29,7 @@ pnpm install
|
||||
echo "forge build..."
|
||||
forge build
|
||||
|
||||
# 5. Export environment variables
|
||||
# 4. Export environment variables
|
||||
export RCL_URL=$RCL_URL
|
||||
export PRIVATE_KEY=$PRIVATE_KEY
|
||||
export ETH_FROM=$ETH_FROM
|
||||
@ -41,5 +38,23 @@ export API_KEY_ETHERSCAN=123
|
||||
export API_KEY_CARDONA=123
|
||||
export API_KEY_LINEASCAN=123
|
||||
|
||||
# 6. Deploy the contract
|
||||
forge script script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --broadcast -vv --private-key $PRIVATE_KEY --sender $ETH_FROM
|
||||
# 5. Deploy the TestToken
|
||||
echo "\nDeploying TestToken (ERC20 Token Contract)...\n"
|
||||
forge script test/TestToken.sol --broadcast -vv --rpc-url http://foundry:8545 --tc TestTokenFactory --private-key $PRIVATE_KEY
|
||||
export TOKEN_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
|
||||
|
||||
echo "\nDeploying LinearPriceCalculator Contract..."
|
||||
forge script script/Deploy.s.sol --broadcast -vv --rpc-url http://foundry:8545 --tc DeployPriceCalculator --private-key $PRIVATE_KEY
|
||||
|
||||
echo "\nDeploying RLN contract..."
|
||||
forge script script/Deploy.s.sol --broadcast -vv --rpc-url http://foundry:8545 --tc DeployWakuRlnV2 --private-key $PRIVATE_KEY
|
||||
|
||||
echo "\nDeploying Proxy contract..."
|
||||
forge script script/Deploy.s.sol --broadcast -vvv --rpc-url http://foundry:8545 --tc DeployProxy --private-key $PRIVATE_KEY
|
||||
export CONTRACT_ADDRESS=0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
|
||||
|
||||
# 6. Contract deployment completed
|
||||
echo "\nContract deployment completed successfully"
|
||||
echo "TOKEN_ADDRESS: $TOKEN_ADDRESS"
|
||||
echo "CONTRACT_ADDRESS: $CONTRACT_ADDRESS"
|
||||
echo "\nEach account registering a membership needs to first mint the token and approve the contract to spend it on their behalf."
|
||||
@ -26,6 +26,8 @@ services:
|
||||
--allow-origin=*
|
||||
--block-time=12
|
||||
--chain-id=1234
|
||||
--gas-limit=30000000
|
||||
--gas-price=1
|
||||
--silent
|
||||
--config-out=/shared/anvil-config.txt
|
||||
volumes:
|
||||
@ -38,16 +40,17 @@ services:
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: '${WATCHTOWER_ENABLED:-false}'
|
||||
environment:
|
||||
- RLN_CONTRACT_REPO_COMMIT=${RLN_CONTRACT_REPO_COMMIT:-64df4593c6a14e43b8b0e9b396d2f4772bb08b34}
|
||||
- RLN_CONTRACT_REPO_COMMIT=${RLN_CONTRACT_REPO_COMMIT:-ad0dc9a81d892864ac2576d74e628ce93da592ef}
|
||||
- PRIVATE_KEY=${PRIVATE_KEY}
|
||||
- RPC_URL=${RPC_URL:-http://foundry:8545}
|
||||
- ETH_FROM=${ETH_FROM}
|
||||
- MAX_MESSAGE_LIMIT=${MAX_MESSAGE_LIMIT:-20}
|
||||
- NUM_NWAKU_NODES=${NUM_NWAKU_NODES:-5}
|
||||
entrypoint: sh
|
||||
command:
|
||||
- '/opt/deploy_rln_contract.sh'
|
||||
volumes:
|
||||
- ./deploy_rln_contract.sh:/opt/deploy_rln_contract.sh
|
||||
- privatekeys-volume:/shared
|
||||
depends_on:
|
||||
- foundry
|
||||
networks:
|
||||
@ -82,16 +85,41 @@ services:
|
||||
entrypoint: sh
|
||||
environment:
|
||||
- RPC_URL=${RPC_URL:-http://foundry:8545}
|
||||
- RLN_CONTRACT_ADDRESS=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
|
||||
- RLN_CONTRACT_ADDRESS=0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
|
||||
- RLN_CREDENTIAL_PATH=/keystore.json
|
||||
- RLN_CREDENTIAL_PASSWORD=passw123
|
||||
- RLN_RELAY_MSG_LIMIT=${RLN_RELAY_MSG_LIMIT:-10}
|
||||
- RLN_RELAY_EPOCH_SEC=${RLN_RELAY_EPOCH_SEC:-60}
|
||||
- RLN_RELAY_MSG_LIMIT=${RLN_RELAY_MSG_LIMIT:-100}
|
||||
- RLN_RELAY_EPOCH_SEC=${RLN_RELAY_EPOCH_SEC:-600}
|
||||
- TOKEN_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
|
||||
- PRIVATE_KEY=${PRIVATE_KEY}
|
||||
command:
|
||||
- '/opt/run_nwaku.sh'
|
||||
volumes:
|
||||
- ./run_nwaku.sh:/opt/run_nwaku.sh:Z
|
||||
- privatekeys-volume:/shared
|
||||
init: true
|
||||
depends_on:
|
||||
contract-repo-deployer:
|
||||
condition: service_completed_successfully
|
||||
nwaku-token-init:
|
||||
condition: service_completed_successfully
|
||||
networks:
|
||||
- simulation
|
||||
|
||||
nwaku-token-init:
|
||||
build:
|
||||
context: ./tools/token-mint-service
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
- RPC_URL=${RPC_URL:-http://foundry:8545}
|
||||
- TOKEN_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
|
||||
- CONTRACT_ADDRESS=0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
|
||||
- PRIVATE_KEY=${PRIVATE_KEY}
|
||||
- NUM_NWAKU_NODES=${NUM_NWAKU_NODES:-5}
|
||||
deploy:
|
||||
replicas: ${NUM_NWAKU_NODES:-5}
|
||||
volumes:
|
||||
- privatekeys-volume:/shared
|
||||
depends_on:
|
||||
contract-repo-deployer:
|
||||
condition: service_completed_successfully
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
ANVIL_CONFIG_PATH=${ANVIL_CONFIG_PATH:-/shared/anvil-config.txt}
|
||||
|
||||
# Check Linux Distro Version - it can differ depending on the nwaku image used
|
||||
OS=$(cat /etc/os-release)
|
||||
if echo $OS | grep -q "Debian"; then
|
||||
@ -49,7 +51,7 @@ fi
|
||||
get_private_key(){
|
||||
|
||||
# Read the JSON file
|
||||
json_content=$(cat /shared/anvil-config.txt)
|
||||
json_content=$(cat "$ANVIL_CONFIG_PATH")
|
||||
|
||||
# Check if json_content has a value
|
||||
if [ -z "$json_content" ]; then
|
||||
|
||||
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"]
|
||||
51
tools/token-mint-service/get_account_key.sh
Normal file
51
tools/token-mint-service/get_account_key.sh
Normal file
@ -0,0 +1,51 @@
|
||||
#!/bin/sh
|
||||
# Helper script to get the index of the container and use it to retrieve a unique account private key.
|
||||
# Each node uses a unique Ethereum account to register with the RLN contract.
|
||||
# The account and private key pairs are stored in anvil-config.txt on a shared volume at anvil startup in the foundry service
|
||||
|
||||
set -e
|
||||
|
||||
ANVIL_CONFIG_PATH=${ANVIL_CONFIG_PATH:-/shared/anvil-config.txt}
|
||||
|
||||
# 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
|
||||
196
tools/token-mint-service/init_node_tokens.py
Normal file
196
tools/token-mint-service/init_node_tokens.py
Normal file
@ -0,0 +1,196 @@
|
||||
#!/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')
|
||||
# The values for NODE_PRIVATE_KEY, NODE_ADDRESS, and NODE_INDEX are set by the get_account_key.sh script
|
||||
self.private_key = os.getenv('NODE_PRIVATE_KEY')
|
||||
self.node_address = os.getenv('NODE_ADDRESS')
|
||||
self.node_index = os.getenv('NODE_INDEX', '0')
|
||||
|
||||
self.mint_amount = int(os.getenv('MINT_AMOUNT', '5000000000000000000')) # at least 5 tokens required for membership with RLN_RELAY_MSG_LIMIT=100
|
||||
|
||||
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
|
||||
@ -4,7 +4,7 @@ NWAKU_IMAGE=harbor.status.im/wakuorg/nwaku:latest
|
||||
NUM_NWAKU_NODES=50
|
||||
# Simulation traffic.
|
||||
MSG_SIZE_KBYTES=10
|
||||
TRAFFIC_DELAY_SECONDS=30
|
||||
TRAFFIC_DELAY_SECONDS=6
|
||||
# Enable automatic Docker image updates.
|
||||
WATCHTOWER_ENABLED=true
|
||||
# Anvil RPC Node external IP and port
|
||||
@ -12,8 +12,7 @@ RPC_URL=http://foundry:8545
|
||||
# Contract-deployment
|
||||
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
||||
ETH_FROM=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
|
||||
MAX_MESSAGE_LIMIT=100
|
||||
# RLNv2 limits
|
||||
RLN_RELAY_MSG_LIMIT=100
|
||||
RLN_RELAY_EPOCH_SEC=600
|
||||
RLN_CONTRACT_REPO_COMMIT=64df4593c6a14e43b8b0e9b396d2f4772bb08b34
|
||||
RLN_CONTRACT_REPO_COMMIT=ad0dc9a81d892864ac2576d74e628ce93da592ef
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user