- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control. - Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities. - Created .gitmodules to include OpenZeppelin contracts as a submodule. - Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment. - Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks. - Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring. - Created scripts for resource import and usage validation across non-US regions. - Added tests for CCIP error handling and integration to ensure robust functionality. - Included various new files and directories for the orchestration portal and deployment scripts.
201 lines
7.6 KiB
JavaScript
Executable File
201 lines
7.6 KiB
JavaScript
Executable File
#!/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;
|
|
|