Add Oracle Aggregator and CCIP Integration

- 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.
This commit is contained in:
defiQUG
2025-12-12 14:57:48 -08:00
parent a1466e4005
commit 1fb7266469
1720 changed files with 241279 additions and 16 deletions

View File

@@ -0,0 +1,200 @@
#!/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;

View File

@@ -0,0 +1,290 @@
#!/usr/bin/env node
/**
* Transaction Mirror Service
*
* Off-chain service to mirror Chain-138 transactions to TransactionMirror
* contract on Ethereum Mainnet for Etherscan visibility.
*
* Usage:
* node scripts/offchain/transaction-mirror-service.js
*
* Environment Variables:
* - CHAIN_138_RPC: RPC endpoint for Chain-138
* - ETHEREUM_MAINNET_RPC: RPC endpoint for Ethereum Mainnet
* - TRANSACTION_MIRROR_ADDRESS: TransactionMirror contract address
* - PRIVATE_KEY: Private key for signing transactions
* - MIRROR_INTERVAL: Block interval for mirroring (default: 10)
* - BATCH_SIZE: Number of transactions per batch (default: 50, max: 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 || '',
TRANSACTION_MIRROR_ADDRESS: process.env.TRANSACTION_MIRROR_ADDRESS || '',
PRIVATE_KEY: process.env.PRIVATE_KEY || '',
MIRROR_INTERVAL: parseInt(process.env.MIRROR_INTERVAL || '10'),
BATCH_SIZE: Math.min(parseInt(process.env.BATCH_SIZE || '50'), 100),
START_BLOCK: process.env.START_BLOCK ? parseInt(process.env.START_BLOCK) : null,
STATE_FILE: path.join(__dirname, '../../data/transaction-mirror-state.json'),
};
// TransactionMirror ABI (simplified)
const TRANSACTION_MIRROR_ABI = [
"function mirrorTransaction(bytes32 txHash, address from, address to, uint256 value, uint256 blockNumber, uint256 blockTimestamp, uint256 gasUsed, bool success, bytes calldata data) external",
"function mirrorBatchTransactions(bytes32[] calldata txHashes, address[] calldata froms, address[] calldata tos, uint256[] calldata values, uint256[] calldata blockNumbers, uint256[] calldata blockTimestamps, uint256[] calldata gasUseds, bool[] calldata successes, bytes[] calldata datas) external",
"function isMirrored(bytes32 txHash) external view returns (bool)",
"function paused() external view returns (bool)",
"event TransactionMirrored(bytes32 indexed txHash, address indexed from, address indexed to, uint256 value, uint256 blockNumber, uint256 blockTimestamp, uint256 gasUsed, bool success)"
];
class TransactionMirrorService {
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.mirrorContract = new ethers.Contract(
CONFIG.TRANSACTION_MIRROR_ADDRESS,
TRANSACTION_MIRROR_ABI,
this.wallet
);
this.lastMirroredBlock = this.loadState();
this.pendingTransactions = [];
}
loadState() {
try {
if (fs.existsSync(CONFIG.STATE_FILE)) {
const data = JSON.parse(fs.readFileSync(CONFIG.STATE_FILE, 'utf8'));
return data.lastMirroredBlock || (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({
lastMirroredBlock: blockNumber,
lastUpdate: new Date().toISOString()
}, null, 2));
} catch (error) {
console.error('Error saving state:', error);
}
}
async getChain138Transactions(blockNumber) {
try {
const block = await this.chain138Provider.getBlock(blockNumber, true);
if (!block || !block.transactions) {
return [];
}
const transactions = [];
for (const txHash of block.transactions) {
try {
const tx = await this.chain138Provider.getTransaction(txHash);
const receipt = await this.chain138Provider.getTransactionReceipt(txHash);
if (tx && receipt) {
transactions.push({
hash: txHash,
from: tx.from,
to: tx.to || ethers.ZeroAddress,
value: tx.value.toString(),
blockNumber: block.number,
blockTimestamp: block.timestamp,
gasUsed: receipt.gasUsed.toString(),
success: receipt.status === 1,
data: tx.data || '0x',
});
}
} catch (error) {
console.warn(`Error fetching transaction ${txHash}:`, error.message);
}
}
return transactions;
} catch (error) {
console.error(`Error fetching Chain-138 block ${blockNumber}:`, error);
return [];
}
}
async mirrorTransaction(tx) {
try {
// Check if contract is paused
const paused = await this.mirrorContract.paused();
if (paused) {
console.warn('TransactionMirror is paused, skipping mirror');
return false;
}
// Check if already mirrored
const isMirrored = await this.mirrorContract.isMirrored(tx.hash);
if (isMirrored) {
return true; // Already mirrored
}
// Mirror transaction
console.log(`Mirroring transaction ${tx.hash}...`);
const txResponse = await this.mirrorContract.mirrorTransaction(
tx.hash,
tx.from,
tx.to,
tx.value,
tx.blockNumber,
tx.blockTimestamp,
tx.gasUsed,
tx.success,
tx.data,
{ gasLimit: 200000 }
);
console.log(`Transaction sent: ${txResponse.hash}`);
const receipt = await txResponse.wait();
console.log(`Transaction ${tx.hash} mirrored in transaction ${receipt.hash}`);
return true;
} catch (error) {
console.error(`Error mirroring transaction ${tx.hash}:`, error);
return false;
}
}
async mirrorBatch(transactions) {
try {
// Check if contract is paused
const paused = await this.mirrorContract.paused();
if (paused) {
console.warn('TransactionMirror is paused, skipping batch mirror');
return false;
}
// Filter out already mirrored transactions
const toMirror = [];
for (const tx of transactions) {
const isMirrored = await this.mirrorContract.isMirrored(tx.hash);
if (!isMirrored) {
toMirror.push(tx);
}
}
if (toMirror.length === 0) {
console.log('All transactions already mirrored');
return true;
}
// Prepare batch data
const txHashes = toMirror.map(tx => tx.hash);
const froms = toMirror.map(tx => tx.from);
const tos = toMirror.map(tx => tx.to);
const values = toMirror.map(tx => tx.value);
const blockNumbers = toMirror.map(tx => tx.blockNumber);
const blockTimestamps = toMirror.map(tx => tx.blockTimestamp);
const gasUseds = toMirror.map(tx => tx.gasUsed);
const successes = toMirror.map(tx => tx.success);
const datas = toMirror.map(tx => tx.data);
console.log(`Mirroring batch of ${toMirror.length} transactions...`);
const txResponse = await this.mirrorContract.mirrorBatchTransactions(
txHashes,
froms,
tos,
values,
blockNumbers,
blockTimestamps,
gasUseds,
successes,
datas,
{ gasLimit: 2000000 } // Higher gas limit for batch
);
console.log(`Batch transaction sent: ${txResponse.hash}`);
const receipt = await txResponse.wait();
console.log(`Batch mirrored in transaction ${receipt.hash}`);
return true;
} catch (error) {
console.error('Error mirroring batch:', error);
return false;
}
}
async run() {
console.log('Transaction Mirror Service starting...');
console.log(`TransactionMirror: ${CONFIG.TRANSACTION_MIRROR_ADDRESS}`);
console.log(`Last mirrored block: ${this.lastMirroredBlock}`);
console.log(`Mirror interval: ${CONFIG.MIRROR_INTERVAL} blocks`);
console.log(`Batch size: ${CONFIG.BATCH_SIZE} transactions`);
console.log('');
while (true) {
try {
// Get latest Chain-138 block
const latestBlock = await this.chain138Provider.getBlockNumber();
const targetBlock = this.lastMirroredBlock + CONFIG.MIRROR_INTERVAL;
if (targetBlock <= latestBlock) {
console.log(`Processing block ${targetBlock} (latest: ${latestBlock})`);
const transactions = await this.getChain138Transactions(targetBlock);
if (transactions.length > 0) {
// Add to pending batch
this.pendingTransactions.push(...transactions);
// Mirror batch if we have enough transactions
if (this.pendingTransactions.length >= CONFIG.BATCH_SIZE) {
const batch = this.pendingTransactions.splice(0, CONFIG.BATCH_SIZE);
await this.mirrorBatch(batch);
}
}
this.lastMirroredBlock = targetBlock;
this.saveState(targetBlock);
} else {
// Mirror any pending transactions if we've been waiting
if (this.pendingTransactions.length > 0 &&
(latestBlock - targetBlock) > CONFIG.MIRROR_INTERVAL) {
const batch = this.pendingTransactions.splice(0, CONFIG.BATCH_SIZE);
await this.mirrorBatch(batch);
}
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.TRANSACTION_MIRROR_ADDRESS || !CONFIG.PRIVATE_KEY) {
console.error('Missing required environment variables:');
console.error(' - ETHEREUM_MAINNET_RPC');
console.error(' - TRANSACTION_MIRROR_ADDRESS');
console.error(' - PRIVATE_KEY');
process.exit(1);
}
const service = new TransactionMirrorService();
service.run().catch(console.error);
}
module.exports = TransactionMirrorService;