Files
smom-dbis-138/scripts/offchain/state-anchor-service.js

201 lines
7.6 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* State Anchor Service
*
* Off-chain service to collect Chain-138 state proofs and anchor them
* to MainnetTether contract on Ethereum Mainnet.
*
* Usage:
* node scripts/offchain/state-anchor-service.js
*
* Environment Variables:
* - CHAIN_138_RPC: RPC endpoint for Chain-138
* - ETHEREUM_MAINNET_RPC: RPC endpoint for Ethereum Mainnet
* - MAINNET_TETHER_ADDRESS: MainnetTether contract address
* - PRIVATE_KEY: Private key for signing transactions
* - ANCHOR_INTERVAL: Block interval for anchoring (default: 100)
* - START_BLOCK: Starting block number (default: latest)
*/
const { ethers } = require('ethers');
const fs = require('fs');
const path = require('path');
// Configuration
const CONFIG = {
CHAIN_138_RPC: process.env.CHAIN_138_RPC || 'http://localhost:8545',
ETHEREUM_MAINNET_RPC: process.env.ETHEREUM_MAINNET_RPC || '',
MAINNET_TETHER_ADDRESS: process.env.MAINNET_TETHER_ADDRESS || '',
PRIVATE_KEY: process.env.PRIVATE_KEY || '',
ANCHOR_INTERVAL: parseInt(process.env.ANCHOR_INTERVAL || '100'),
START_BLOCK: process.env.START_BLOCK ? parseInt(process.env.START_BLOCK) : null,
STATE_FILE: path.join(__dirname, '../../data/state-anchor-state.json'),
};
// MainnetTether ABI (simplified)
const MAINNET_TETHER_ABI = [
"function anchorStateProof(uint256 blockNumber, bytes32 blockHash, bytes32 stateRoot, bytes32 previousBlockHash, uint256 timestamp, bytes calldata signatures, uint256 validatorCount) external",
"function isAnchored(uint256 blockNumber) external view returns (bool)",
"function paused() external view returns (bool)",
"event StateProofAnchored(uint256 indexed blockNumber, bytes32 indexed blockHash, bytes32 indexed stateRoot, uint256 timestamp, uint256 validatorCount)"
];
class StateAnchorService {
constructor() {
this.chain138Provider = new ethers.JsonRpcProvider(CONFIG.CHAIN_138_RPC);
this.mainnetProvider = new ethers.JsonRpcProvider(CONFIG.ETHEREUM_MAINNET_RPC);
this.wallet = new ethers.Wallet(CONFIG.PRIVATE_KEY, this.mainnetProvider);
this.tetherContract = new ethers.Contract(
CONFIG.MAINNET_TETHER_ADDRESS,
MAINNET_TETHER_ABI,
this.wallet
);
this.lastAnchoredBlock = this.loadState();
}
loadState() {
try {
if (fs.existsSync(CONFIG.STATE_FILE)) {
const data = JSON.parse(fs.readFileSync(CONFIG.STATE_FILE, 'utf8'));
return data.lastAnchoredBlock || (CONFIG.START_BLOCK || 0);
}
} catch (error) {
console.error('Error loading state:', error);
}
return CONFIG.START_BLOCK || 0;
}
saveState(blockNumber) {
try {
const dir = path.dirname(CONFIG.STATE_FILE);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(CONFIG.STATE_FILE, JSON.stringify({
lastAnchoredBlock: blockNumber,
lastUpdate: new Date().toISOString()
}, null, 2));
} catch (error) {
console.error('Error saving state:', error);
}
}
async getChain138Block(blockNumber) {
try {
const block = await this.chain138Provider.getBlock(blockNumber, true);
return {
number: block.number,
hash: block.hash,
stateRoot: block.stateRoot || ethers.ZeroHash, // Fallback if not available
parentHash: block.parentHash,
timestamp: block.timestamp,
};
} catch (error) {
console.error(`Error fetching Chain-138 block ${blockNumber}:`, error);
return null;
}
}
async collectSignatures(blockNumber) {
// Placeholder: In production, collect validator signatures
// This would involve querying Chain-138 validators
return ethers.toUtf8Bytes('signatures-placeholder');
}
async anchorStateProof(blockData) {
try {
// Check if contract is paused
const paused = await this.tetherContract.paused();
if (paused) {
console.warn('MainnetTether is paused, skipping anchor');
return false;
}
// Check if already anchored
const isAnchored = await this.tetherContract.isAnchored(blockData.number);
if (isAnchored) {
console.log(`Block ${blockData.number} already anchored`);
return true;
}
// Collect signatures (placeholder)
const signatures = await this.collectSignatures(blockData.number);
const validatorCount = 1; // Placeholder
// Anchor state proof
console.log(`Anchoring block ${blockData.number}...`);
const tx = await this.tetherContract.anchorStateProof(
blockData.number,
blockData.hash,
blockData.stateRoot,
blockData.parentHash,
blockData.timestamp,
signatures,
validatorCount,
{ gasLimit: 500000 }
);
console.log(`Transaction sent: ${tx.hash}`);
const receipt = await tx.wait();
console.log(`Block ${blockData.number} anchored in transaction ${receipt.hash}`);
return true;
} catch (error) {
console.error(`Error anchoring block ${blockData.number}:`, error);
return false;
}
}
async run() {
console.log('State Anchor Service starting...');
console.log(`MainnetTether: ${CONFIG.MAINNET_TETHER_ADDRESS}`);
console.log(`Last anchored block: ${this.lastAnchoredBlock}`);
console.log(`Anchor interval: ${CONFIG.ANCHOR_INTERVAL} blocks`);
console.log('');
while (true) {
try {
// Get latest Chain-138 block
const latestBlock = await this.chain138Provider.getBlockNumber();
const targetBlock = this.lastAnchoredBlock + CONFIG.ANCHOR_INTERVAL;
if (targetBlock <= latestBlock) {
console.log(`Processing block ${targetBlock} (latest: ${latestBlock})`);
const blockData = await this.getChain138Block(targetBlock);
if (blockData) {
const success = await this.anchorStateProof(blockData);
if (success) {
this.lastAnchoredBlock = targetBlock;
this.saveState(targetBlock);
}
}
} else {
console.log(`Waiting for block ${targetBlock} (current: ${latestBlock})`);
await new Promise(resolve => setTimeout(resolve, 30000)); // Wait 30 seconds
}
} catch (error) {
console.error('Error in main loop:', error);
await new Promise(resolve => setTimeout(resolve, 10000)); // Wait 10 seconds on error
}
}
}
}
// Run service
if (require.main === module) {
if (!CONFIG.ETHEREUM_MAINNET_RPC || !CONFIG.MAINNET_TETHER_ADDRESS || !CONFIG.PRIVATE_KEY) {
console.error('Missing required environment variables:');
console.error(' - ETHEREUM_MAINNET_RPC');
console.error(' - MAINNET_TETHER_ADDRESS');
console.error(' - PRIVATE_KEY');
process.exit(1);
}
const service = new StateAnchorService();
service.run().catch(console.error);
}
module.exports = StateAnchorService;