Somnia 백서

Somnia

원문

bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 54쪽 중 39쪽

value: string; gasUsed: string; status: string; timestamp: string; } export interface BlockData { number: number; hash: string; timestamp: number; transactions: string[]; gasUsed: string; gasLimit: string; miner: string; } export class SomniaExplorerService { private api: AxiosInstance; private network = getCurrentNetwork(); constructor() { this.api = axios.create({ baseURL: this.network.explorerApiUrl, timeout: 10000, headers: { 'Content-Type': 'application/json', 'User-Agent': 'Somnia-Explorer-Client/1.0', 'Cache-Control': 'no-cache' // Ensure fresh data for health checks } }); // Add request interceptor for comprehensive logging this.api.interceptors.request.use( (config) => { logger.info('Somnia Explorer API Request', { method: config.method?.toUpperCase(), url: config.url, baseURL: config.baseURL, timestamp: new Date().toISOString(), network: this.network.name }); return config; }, (error) => { logger.error('Somnia Explorer API Request Error', { error: error.message, timestamp: new Date().toISOString(), network: this.network.name }); return Promise.reject(error); } ); // Add response interceptor for logging this.api.interceptors.response.use( (response) => { logger.info('Somnia Explorer API Response', { status: response.status, url: response.config.url, responseTime: response.headers['x-response-time'] || 'unknown', timestamp: new Date().toISOString(), network: this.network.name }); return response; }, (error) => { logger.error('Somnia Explorer API Response Error', { status: error.response?.status, statusText: error.response?.statusText, url: error.config?.url, message: error.message, timestamp: new Date().toISOString(), network: this.network.name }); return Promise.reject(error); } ); } async getTransaction(txHash: string): Promise<TransactionData | null> { try { const response = await this.api.get(`/v2/transactions/${txHash}`); logger.info('Successfully retrieved transaction', { txHash, blockNumber: response.data.block, network: this.network.name }); return response.data; } catch (error: any) { logger.error('Failed to fetch transaction', { txHash, error: error.message, network: this.network.name }); return null; } } async getBlock(blockNumber: number): Promise<BlockData | null> { try { const response = await this.api.get(`/v2/blocks/${blockNumber}`); logger.info('Successfully retrieved block', { blockNumber, hash: response.data.hash, txCount: response.data.tx_count, network: this.network.name }); return response.data; } catch (error: any) { logger.error('Failed to fetch block', { blockNumber, error: error.message, network: this.network.name }); return null; } } async getAddressTransactions(address: string, page = 1, limit = 20): Promise<TransactionData[]> { try { const response = await this.api.get(`/v2/addresses/${address}/transactions`, { params: { page, limit } }); logger.info('Retrieved address transactions', { address, count: response.data.items?.length || 0, page, limit, network: this.network.name }); return response.data.items || []; } catch (error: any) { logger.error('Failed to fetch address transactions', { address, error: error.message, network: this.network.name }); return []; } } } ``` </details> This `SomniaExplorerService` class provides comprehensive logging for all API interactions and follows Blockscout's REST API patterns. The service includes: * **Type-safe interfaces** for transaction and block data * **Axios interceptors** for automatic request/response logging * **Error handling** with detailed logging for debugging * **Network-aware configuration** that adapts to different Somnia environments Tip: The service automatically logs all API requests and responses, making it easy to debug issues and monitor performance. ### Logging System <details> <summary>src/utils/logger.ts</summary> ```javascript // src/utils/logger.ts import winston from 'winston'; import path from 'path'; // Custom log format for Somnia applications const somniaLogFormat = winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.json(), winston.format.printf(({ timestamp, level, message, ...meta }) => { return JSON.stringify({ timestamp, level, message, service: 'somnia-explorer-client', network: process.env.NODE_ENV === 'production' ? 'mainnet' : 'testnet', ...meta }); }) ); // Create logger instance with Somnia-specific configuration export const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: somniaLogFormat, defaultMeta: { service: 'somnia-explorer-integration', version: '1.0.0', chain: 'somnia' }, transports: [ // Console transport for development new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ) }), // File transport for all logs new winston.transports.File({ filename: path.join('logs', 'somnia-explorer.log'), maxsize: 5242880, // 5MB maxFiles: 5 }), // Separate file for API-specific logs new winston.transports.File({ filename: path.join('logs', 'api-requests.log'), level: 'info', maxsize: 5242880, maxFiles: 3, format: winston.format.combine( winston.format.timestamp(), winston.format.json() ) }), // Separate file for errors new winston.transports.File({ filename: path.join('logs', 'error.log'), level: 'error', maxsize: 5242880, maxFiles: 3 }) ], // Handle uncaught exceptions exceptionHandlers: [ new winston.transports.File({ filename: path.join('logs', 'exceptions.log') }) ], // Handle unhandled promise rejections rejectionHandlers: [ new winston.transports.File({ filename: path.join('logs', 'rejections.log') }) ] }); // Create specialized loggers for different components export const apiLogger = logger.child({ component: 'api' }); export const explorerLogger = logger.child({ component: 'explorer' }); export const healthLogger = logger.child({ component: 'health' }); ``` </details> **Winston Configuration** This Winston configuration provides several enterprise-grade features that make it production-ready: * **Structured JSON logs**: logs are formatted as JSON objects with consistent fields like `timestamp`, `level`, `message`, and `metadata`. This makes it easy to parse logs programmatically, search for specific events, and integrate with log analysis tools like ELK Stack or Splunk. * **File rotation**: Automatically manages log file sizes by creating new files when they reach 5MB and keeping only the 5 most recent files. * **Component-specific loggers**: Creates separate logger instances for different parts of your application (API, Explorer, Health) using `logger.child()`. * **Error tracking with stack traces**: Captures complete error information including stack traces using `winston.format.errors({ stack: true })`. * **Performance metrics**: Logs response times and request metadata, allowing monitoring of API performance. * **Separate log files by purpose**: Different files for general logs, API requests, errors, exceptions, and promise rejections. {% hint style="danger" %} **Warning**: Ensure the `logs` directory exists before starting your application, or Winston will fail to write log files. {% endhint %} ### Usage Examples **Structured Logging Output** ```json { "timestamp": "2024-01-15 10:30:45", "level": "info", "message": "Somnia Explorer API Request", "service": "somnia-explorer-integration", "network": "testnet", "component": "explorer", "method": "GET", "url": "/v2/transactions/0x1234...", "baseURL": "https://shannon-explorer.somnia.network/api", "chain": "somnia" } ``` **API Service Usage** ```typescript // Initialize the explorer service const explorerService = new SomniaExplorerService(); // Get transaction data const txData = await explorerService.getTransaction('0x1234...'); if (txData) { console.log('Transaction found:', txData.hash); } // Get block data const blockData = await explorerService.getBlock(12345); if (blockData) { console.log('Block transactions:', blockData.transactions.length); } // Get address transactions const addressTxs = await explorerService.getAddressTransactions( '0xabcd...', 1, // page 10 // limit ); console.log('Address transactions:', addressTxs.length); ``` ## Troubleshooting <details> <summary>If API requests fail</summary> * Verify Somnia Network explorer endpoints are accessible * Check network connectivity to shannon-explorer.somnia.network or explorer.somnia.network * Ensure correct API endpoint format matches Blockscout v2 schema </details> <details> <summary>If logging doesn't work</summary> * Ensure the logs directory exists: `mkdir logs` * Check file permissions for log files * Verify Winston configuration matches your environment </details> You've successfully implemented: * **Comprehensive API integration** with the Somnia Explorer using type-safe interfaces * **Enterprise-grade logging** with Winston, including structured JSON output and file rotation * **Error handling and monitoring** for production-ready applications * **Performance tracking** with request/response logging and timing metrics Your application now has robust blockchain data access with proper logging infrastructure that will help you monitor, debug, and maintain your Somnia-based applications in production. {% hint style="success" %} [Sample Project](https://github.com/PromiseGameFi/Somnia-sample-project/tree/main/somnia-explorer-integration) {% endhint %} # Somnia Gas Differences To Ethereum Somnia's gas costs work very differently to Ethereum's. For background: EVM gas fees are designed to approximate the load placed on the blockchain. They are used to charge users in proportion to the system resources they consume. A transaction's gas fee (in ETH or SOMI) is calculated as `number of units of gas * price per unit of gas`. The number of units of gas is just the sum of the gas costs of the transaction's operations. Because the price of a unit of gas is variable, the absolute cost of an operation in units of gas does not ultimately matter. What matters is the relative difference between operations' gas costs. In other words, if you doubled the gas costs for all operations, and halved the price per unit of gas, there would be no functional change. In regards to determining gas costs, there are two fundamental differences between Ethereum and Somnia: 1. Somnia's architecture and implementation has drastically reduced the real-world performance cost of most EVM operations, but not all. A few operations, such as third-party library precompiles, are just as performant on Ethereum as on Somnia - both use the same code. This relative difference requires us to adjust the relative gas costs of these operations. We could either reduce the gas costs of all operations that Somnia has improved, or increase the gas costs of all operations that Somnia has *not* improved - the result is equivalent. Somnia does the latter, as it fits better into the Ethereum developer ecosystem. 2. Ethereum charges a flat gas cost to read any existing value from storage for the first time in a transaction. This ends up hitting a database whose latency for this read can vary by multiple orders of magnitude. This gas cost inaccuracy is a big problem when scaling these blockchains, as it requires them to leave massive amounts of unused buffer in their execution budget to account for these unpredictable latencies. Somnia has built a new database, IceDb, that is designed to fix this problem. Only the (rare) reads which actually take a long time have a high gas cost; reading from cached state is far cheaper. These two factors mean that Somnia requires different gas semantics to Ethereum. The differences are detailed in this document. This document covers the number of units of gas each operation costs. Somnia also differs from Ethereum in the *price* charged per unit of gas: heavy, sustained users of the network automatically pay a discounted base gas price, as described in [Sustained Use Gas Discounts](/developer/deployment-and-production/sustained-use-gas-discounts). ## Precompiles These precompiles have flat multiples applied to [the same gas calculation that Ethereum uses](https://www.evm.codes/precompiled). * `ecRecover (0x01)` costs **50** times the Ethereum calculated gas usage. (i.e. **150,000** gas instead of **3000** gas.) * `SHA2-256 (0x02)` costs **50** times the Ethereum calculated gas usage. * `RIPEMD-160 (0x03)` costs **10** times the Ethereum calculated gas usage. * `modexp (0x05)` costs **10** times the Ethereum calculated gas usage. * `ecAdd (0x06)` costs **50** times the Ethereum calculated gas usage. * `ecMul (0x07)` costs **10** times the Ethereum calculated gas usage. * `ecPairing (0x08)` costs **250** times the Ethereum calculated gas usage. * `blake2f (0x09)` costs **10** times the Ethereum calculated gas usage. * `point evaluation (0x0a)` costs **50** times the Ethereum calculated gas usage. BLS operations: * `BLS12_G1ADD (0x0b)` costs **220** times the Ethereum calculated gas usage. * `BLS12_G1MSM (0x0c)` costs **110** times the Ethereum calculated gas usage. * `BLS12_G2ADD (0x0d)` costs **330** times the Ethereum calculated gas usage. * `BLS12_G2MSM (0x0e)` costs **50** times the Ethereum calculated gas usage. * `BLS12_PAIRING_CHECK (0x0f)` costs **50** times the Ethereum calculated gas usage. * `BLS12_MAP_FP_TO_G1 (0x10)` costs **120** times the Ethereum calculated gas usage. * `BLS12_MAP_FP2_TO_G2 (0x11)` costs **50** times the Ethereum calculated gas usage. ## Non-storage EVM operations * `SELFBALANCE` costs **305** gas instead of **5** gas. * `KECCAK256` costs `1250 + 300 * minimum_word_size` gas instead of `30 + 6 * minimum_word_size` gas. * `ADDMOD` costs **358** gas instead of **8** gas. * `MULMOD` costs **358** gas instead of **8** gas. ## Storage EVM operations Ethereum uses an [access list based gas model](https://www.evm.codes/about#access_list) for all storage operations, to charge extra gas depending on if the value being accessed has already been accessed *in the same transaction*, and if it is creating or deleting that state. As discussed above, Somnia instead charges extra gas based on a more accurate model of the real world latency it takes to read or write the value in question: ### `SLOAD` * If the storage slot key is in the set of most recently accessed **128 million** contract slot keys, **no extra gas is charged** (on top of the static op gas fee, which is **100** gas). * If the key does not exist, the access requires **at least 1,000,000 gas remaining**, but that gas is **not charged**, so there is no extra cost. * Otherwise, the read costs an additional **1,000,000** gas. ### `SSTORE` * If the storage slot key is in the set of most recently accessed **128 million** contract slot keys, **no extra gas is charged** (on top of the static op gas fee, which is **100** gas). * If the key does not exist and is being *written to zero*, the access requires **at least 1,000,000 gas remaining**, but that gas is **not charged**, so there is no extra cost. * If the key does not exist and is being *written to a non-zero value*, the access requires **at least 1,000,000 gas remaining**, but the caller is charged **200,000** gas. * Otherwise, the write costs an additional **1,000,000** gas. ### Account operations The following EVM operations access account state: * `BALANCE` * `EXTCODESIZE` * `EXTCODECOPY` * `EXTCODEHASH` * `CALL` * `CALLCODE` * `DELEGATECALL` * `STATICCALL` * `CREATE` * `CREATE2` * `SELFDESTRUCT` These have an additional storage gas fee (in place of Ethereum's dynamic access list gas fee): * If the account is in the set of most recently accessed **32 million** accounts, **no extra gas is charged**. * If the account does not exist and is being *read*, the access requires **at least 1,000,000 gas remaining**, but that gas is **not charged**, so there is no extra cost. * If the account does not exist and is being *created*, the access requires **at least 1,000,000 gas remaining**, but the caller is charged **400,000** gas for the new account. * Otherwise, the access costs **1,000,000** gas. ## EVM Logs Somnia charges an increased amount of gas for EVM logs due to the historical storage requirements. Ethereum charges: `375 + 375 * topic_count + 8 * size + memory_expansion_cost` gas Somnia charges: `3200 + 5120 * topic_count + 160 * size + memory_expansion_cost` gas To illustrate these changes with the five different EVM log operations: | EVM Op | Ethereum gas | Somnia gas | | -------------------------- | -----------: | ---------: | | LOG0 with 32 bytes of data | 631 | 8,320 | | LOG1 with 32 bytes of data | 1,006 | 13,440 | | LOG2 with 32 bytes of data | 1,381 | 18,560 | | LOG3 with 32 bytes of data | 1,756 | 23,680 | | LOG4 with 32 bytes of data | 2,131 | 28,800 | ## Contract bytecode deployment cost Ethereum charges **200** gas per byte of deployed bytecode. Somnia charges **3125** gas per byte of deployed bytecode. ## EIP-7702 delegation authorisations Ethereum charges **25,000** gas per authorisation in a [Set Code transaction](https://eips.ethereum.org/EIPS/eip-7702). **12,500** gas is refunded if account creation isn't required. Somnia charges **1,570,000** gas per authorisation in a Set Code transaction. **400,000** gas is refunded if account creation isn't required. # Sustained Use Gas Discounts Somnia automatically reduces the base gas price paid by accounts that consistently consume a lot of gas. The more gas you use over a sustained period, the less you pay for each unit of gas. There is nothing to opt into, claim, stake or sign. Discounts are computed by the network from observed gas usage and applied automatically when your transactions execute. ## How the discount is applied A discount is a percentage off the **base gas price only**. Any priority fee you pay is added on top, undiscounted: ``` price charged per gas = base_gas_price * (100 - discount_percent) / 100 + priority_fee ``` Discounts never reach 100% - gas is never free. The discount changes the **price** you pay per unit of gas. It never changes how many units of gas an operation costs, and it does not raise any gas limit. ## How usage is measured Usage is tracked per **sender address** - the `from` of the transaction, which is the account that pays. Gas consumed by contracts you call counts toward your usage, not theirs. Somnia divides time into **epochs**. On mainnet and testnet an epoch is 3000 ledger blocks, which is roughly **5 minutes** of wall-clock time. * **Every epoch, the gas used by each account is tracked.** An account is only recorded for that epoch if it consumed more than ten million gas units in that epoch, and only the 1000 heaviest gas consumers of the epoch are recorded. * **Your discount is derived from your total tracked gas across the most recent 16 epochs** - a rolling window of roughly **80 minutes**. As each epoch passes, the oldest epoch's usage drops out of the window. * That rolling total is compared against a table of tiers. Each tier names a total amount of gas and the discount earned by reaching it; you get the discount of the highest tier your rolling total reaches. Below the lowest tier, your discount is zero. A newly earned discount applies to every transaction you send from the **start of the next epoch**, so it takes effect at the next 5-minute boundary and reflects roughly your last 80 minutes of activity. ### Current tiers The tier table on mainnet: | Gas units used over the rolling window | Discount | | -------------------------------------- | -------- | | 900,000,000 | 70% | | 4,000,000,000 | 90% | At a base gas price of 6 gwei, sustaining those rates costs roughly **100 SOMI** and **430 SOMI** per day on gas at the undiscounted price. Tiers are network configuration and may
프로젝트로 돌아가기